atem-connection
Version:
Typescript Node.js library for connecting with an ATEM switcher.
211 lines • 12 kB
JavaScript
"use strict";
var _DataTransferManager_nextTransferIdInner, _DataTransferManager_nextTransferId, _DataTransferManager_sendLockCommand, _DataTransferManager_stillsLock, _DataTransferManager_clipLocks, _DataTransferManager_labelsLock, _DataTransferManager_macroLock, _DataTransferManager_rawSendCommands;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataTransferManager = void 0;
const tslib_1 = require("tslib");
const exitHook = require("exit-hook");
const dataTransferQueue_1 = require("./dataTransferQueue");
const dataTransferUploadStill_1 = require("./dataTransferUploadStill");
const dataTransferUploadClip_1 = require("./dataTransferUploadClip");
const dataTransferUploadAudio_1 = require("./dataTransferUploadAudio");
const dataTransferUploadMultiViewerLabel_1 = require("./dataTransferUploadMultiViewerLabel");
const dataTransferDownloadMacro_1 = require("./dataTransferDownloadMacro");
const dataTransferUploadMacro_1 = require("./dataTransferUploadMacro");
const DataTransfer_1 = require("../commands/DataTransfer");
const debug_1 = require("debug");
const dataTransferDownloadStill_1 = require("./dataTransferDownloadStill");
const MAX_PACKETS_TO_SEND_PER_TICK = 50;
const MAX_TRANSFER_INDEX = (1 << 16) - 1; // Inclusive maximum
const debug = (0, debug_1.default)('atem-connection:data-transfer:manager');
class DataTransferManager {
constructor(rawSendCommands) {
_DataTransferManager_nextTransferIdInner.set(this, 0);
_DataTransferManager_nextTransferId.set(this, () => {
var _a, _b;
const index = (tslib_1.__classPrivateFieldSet(this, _DataTransferManager_nextTransferIdInner, (_b = tslib_1.__classPrivateFieldGet(this, _DataTransferManager_nextTransferIdInner, "f"), _a = _b++, _b), "f"), _a);
if (tslib_1.__classPrivateFieldGet(this, _DataTransferManager_nextTransferIdInner, "f") > MAX_TRANSFER_INDEX)
tslib_1.__classPrivateFieldSet(this, _DataTransferManager_nextTransferIdInner, 0, "f");
return index;
});
_DataTransferManager_sendLockCommand.set(this, (/*lock: DataTransferLockingQueue,*/ cmd) => {
tslib_1.__classPrivateFieldGet(this, _DataTransferManager_rawSendCommands, "f").call(this, [cmd]).catch((e) => {
debug(`Failed to send lock command: ${e}`);
});
});
_DataTransferManager_stillsLock.set(this, new dataTransferQueue_1.DataTransferLockingQueue(0, tslib_1.__classPrivateFieldGet(this, _DataTransferManager_sendLockCommand, "f"), tslib_1.__classPrivateFieldGet(this, _DataTransferManager_nextTransferId, "f")));
_DataTransferManager_clipLocks.set(this, new Map()); // clipLocks get dynamically allocated
_DataTransferManager_labelsLock.set(this, new dataTransferQueue_1.DataTransferSimpleQueue(tslib_1.__classPrivateFieldGet(this, _DataTransferManager_nextTransferId, "f")));
_DataTransferManager_macroLock.set(this, new dataTransferQueue_1.DataTransferSimpleQueue(tslib_1.__classPrivateFieldGet(this, _DataTransferManager_nextTransferId, "f")));
_DataTransferManager_rawSendCommands.set(this, void 0);
tslib_1.__classPrivateFieldSet(this, _DataTransferManager_rawSendCommands, rawSendCommands, "f");
}
get allLocks() {
return [tslib_1.__classPrivateFieldGet(this, _DataTransferManager_stillsLock, "f"), ...tslib_1.__classPrivateFieldGet(this, _DataTransferManager_clipLocks, "f").values(), tslib_1.__classPrivateFieldGet(this, _DataTransferManager_labelsLock, "f"), tslib_1.__classPrivateFieldGet(this, _DataTransferManager_macroLock, "f")];
}
/**
* Start sending of commands
* This is called once the connection has received the initial state data
*/
startCommandSending(skipUnlockAll) {
// TODO - abort any active transfers
if (!this.interval) {
// New connection means a new queue
if (!skipUnlockAll) {
debug(`Clearing all held locks`);
for (const lock of this.allLocks) {
lock.clearQueueAndAbort(new Error('Restarting connection'));
}
}
this.interval = setInterval(() => {
for (const lock of this.allLocks) {
const commandsToSend = lock.popQueuedCommands(MAX_PACKETS_TO_SEND_PER_TICK); // Take some, it is unlikely that multiple will run at once
if (commandsToSend && commandsToSend.length > 0) {
// debug(`Sending ${commandsToSend.length} commands `)
tslib_1.__classPrivateFieldGet(this, _DataTransferManager_rawSendCommands, "f").call(this, commandsToSend).catch((e) => {
// Failed to send/queue something, so abort it
lock.tryAbortTransfer(new Error(`Command send failed: ${e}`));
});
}
}
}, 2); // TODO - refine this. perhaps we can stop and restart the interval?
}
if (!this.exitUnsubscribe) {
this.exitUnsubscribe = exitHook(() => {
debug(`Exit auto-cleanup`);
// TODO - replace this with a WeakRef to the parent class?
this.stopCommandSending();
});
}
}
/**
* Stop sending of commands
* This is called once the connection is disconnected
*/
stopCommandSending() {
debug('Stopping command sending');
for (const lock of this.allLocks) {
lock.clearQueueAndAbort(new Error('Stopping connection'));
}
if (this.exitUnsubscribe) {
this.exitUnsubscribe();
this.exitUnsubscribe = undefined;
}
if (this.interval) {
clearInterval(this.interval);
this.interval = undefined;
}
}
/**
* Queue the handling of a received command
* We do it via a queue as some of the handlers need to be async, and we don't want to block state updates from happening in parallel
*/
queueHandleCommand(command) {
debug(`Received command ${command.constructor.name}: ${JSON.stringify(command)}`);
if (command instanceof DataTransfer_1.LockObtainedCommand || command instanceof DataTransfer_1.LockStateUpdateCommand) {
let lock;
if (command.properties.index === 0) {
lock = tslib_1.__classPrivateFieldGet(this, _DataTransferManager_stillsLock, "f");
}
else if (command.properties.index >= 100) {
// Looks like a special lock that we arent expecting
// Ignore it for now
return;
}
else {
lock = tslib_1.__classPrivateFieldGet(this, _DataTransferManager_clipLocks, "f").get(command.properties.index - 1);
}
// Must be a clip that we aren't expecting
if (!lock)
lock = new dataTransferQueue_1.DataTransferLockingQueue(command.properties.index, tslib_1.__classPrivateFieldGet(this, _DataTransferManager_sendLockCommand, "f"), tslib_1.__classPrivateFieldGet(this, _DataTransferManager_nextTransferId, "f"));
// handle actual command
if (command instanceof DataTransfer_1.LockObtainedCommand) {
lock.lockObtained();
}
else if (command instanceof DataTransfer_1.LockStateUpdateCommand) {
lock.updateLock(command.properties.locked);
}
return;
}
// If this command is for a transfer
if (command.properties.transferId !== undefined) {
// try to establish the associated DataLock:
let lock;
for (const _lock of this.allLocks) {
if (_lock.currentTransferId === command.properties.transferId) {
lock = _lock;
}
}
// console.log('CMD', command.constructor.name)
// Doesn't appear to be for a known lock
// TODO - should we fire an abort back just in case?
if (!lock)
return;
lock.handleCommand(command);
// } else {
// // debugging:
// console.log('UNKNOWN COMMAND:', command)
}
}
async downloadClipFrame(clipIndex, frameIndex) {
if (clipIndex < 0 || clipIndex >= 2) {
throw new Error('Invalid clip index');
}
const transfer = new dataTransferDownloadStill_1.DataTransferDownloadStill(clipIndex + 1, frameIndex);
return this.getClipLock(clipIndex).enqueue(transfer);
}
async downloadStill(index) {
const transfer = new dataTransferDownloadStill_1.DataTransferDownloadStill(0, index);
return tslib_1.__classPrivateFieldGet(this, _DataTransferManager_stillsLock, "f").enqueue(transfer);
}
async uploadStill(index, data, name, description) {
const transfer = new dataTransferUploadStill_1.default(index, data, name, description);
return tslib_1.__classPrivateFieldGet(this, _DataTransferManager_stillsLock, "f").enqueue(transfer);
}
async uploadClip(index, data, name) {
const provideFrame = async function* () {
let id = -1;
for await (const frame of data) {
id++;
yield new dataTransferUploadClip_1.DataTransferUploadClipFrame(index, id, frame);
}
return undefined;
};
const transfer = new dataTransferUploadClip_1.DataTransferUploadClip(index, name, provideFrame(), tslib_1.__classPrivateFieldGet(this, _DataTransferManager_nextTransferId, "f"));
const lock = this.getClipLock(index);
return lock.enqueue(transfer);
}
async uploadAudio(index, data, name) {
const transfer = new dataTransferUploadAudio_1.default(index, data, name);
const lock = this.getClipLock(index);
return lock.enqueue(transfer);
}
async downloadMacro(index) {
const transfer = new dataTransferDownloadMacro_1.DataTransferDownloadMacro(index);
return tslib_1.__classPrivateFieldGet(this, _DataTransferManager_macroLock, "f").enqueue(transfer);
}
async uploadMacro(index, data, name) {
const transfer = new dataTransferUploadMacro_1.DataTransferUploadMacro(index, data, name);
return tslib_1.__classPrivateFieldGet(this, _DataTransferManager_macroLock, "f").enqueue(transfer);
}
async uploadMultiViewerLabel(index, data) {
const transfer = new dataTransferUploadMultiViewerLabel_1.default(index, data);
return tslib_1.__classPrivateFieldGet(this, _DataTransferManager_labelsLock, "f").enqueue(transfer);
}
getClipLock(index) {
const lock = tslib_1.__classPrivateFieldGet(this, _DataTransferManager_clipLocks, "f").get(index);
if (lock) {
return lock;
}
else if (index >= 0 && index < 20) {
const lock = new dataTransferQueue_1.DataTransferLockingQueue(index + 1, tslib_1.__classPrivateFieldGet(this, _DataTransferManager_sendLockCommand, "f"), tslib_1.__classPrivateFieldGet(this, _DataTransferManager_nextTransferId, "f"));
tslib_1.__classPrivateFieldGet(this, _DataTransferManager_clipLocks, "f").set(index, lock);
return lock;
}
else {
throw new Error('Invalid clip index');
}
}
}
exports.DataTransferManager = DataTransferManager;
_DataTransferManager_nextTransferIdInner = new WeakMap(), _DataTransferManager_nextTransferId = new WeakMap(), _DataTransferManager_sendLockCommand = new WeakMap(), _DataTransferManager_stillsLock = new WeakMap(), _DataTransferManager_clipLocks = new WeakMap(), _DataTransferManager_labelsLock = new WeakMap(), _DataTransferManager_macroLock = new WeakMap(), _DataTransferManager_rawSendCommands = new WeakMap();
//# sourceMappingURL=index.js.map