atem-connection
Version:
Typescript Node.js library for connecting with an ATEM switcher.
138 lines • 7.01 kB
JavaScript
;
var _DataTransferUploadBuffer_bytesSent;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataTransferUploadBuffer = exports.generateUploadBufferInfo = exports.generateHashForBuffer = void 0;
const tslib_1 = require("tslib");
const DataTransfer_1 = require("../commands/DataTransfer");
const crypto = require("crypto");
const dataTransfer_1 = require("./dataTransfer");
const debug0 = require("debug");
const rgbaToYuv422_1 = require("../lib/converters/rgbaToYuv422");
const rle_1 = require("../lib/converters/rle");
const debug = debug0('atem-connection:data-transfer:upload-buffer');
function generateHashForBuffer(data) {
return data ? crypto.createHash('md5').update(data).digest('base64') : '';
}
exports.generateHashForBuffer = generateHashForBuffer;
function generateUploadBufferInfo(data, resolution, shouldEncodeRLE) {
const expectedLength = resolution.width * resolution.height * 4;
if (Buffer.isBuffer(data)) {
if (data.length !== expectedLength)
throw new Error(`Pixel buffer has incorrect length. Received ${data.length} expected ${expectedLength}`);
const encodedData = (0, rgbaToYuv422_1.convertRGBAToYUV422)(resolution.width, resolution.height, data);
return {
encodedData: shouldEncodeRLE ? (0, rle_1.encodeRLE)(encodedData) : encodedData,
rawDataLength: encodedData.length,
isRleEncoded: shouldEncodeRLE,
hash: generateHashForBuffer(encodedData),
};
}
else {
const result = { ...data };
if (data.rawDataLength !== expectedLength)
throw new Error(`Pixel buffer has incorrect length. Received ${data.rawDataLength} expected ${expectedLength}`);
if (shouldEncodeRLE && !data.isRleEncoded) {
data.isRleEncoded = true;
data.encodedData = (0, rle_1.encodeRLE)(data.encodedData);
}
return result;
}
}
exports.generateUploadBufferInfo = generateUploadBufferInfo;
class DataTransferUploadBuffer extends dataTransfer_1.DataTransfer {
constructor(buffer) {
super();
_DataTransferUploadBuffer_bytesSent.set(this, 0);
this.hash = buffer.hash ?? generateHashForBuffer(buffer.encodedData);
this.data = buffer.encodedData;
}
async handleCommand(command, oldState) {
if (command instanceof DataTransfer_1.DataTransferErrorCommand) {
switch (command.properties.errorCode) {
case DataTransfer_1.ErrorCode.Retry:
return this.restartTransfer(command.properties.transferId);
case DataTransfer_1.ErrorCode.NotFound:
this.abort(new Error('Invalid upload'));
return {
newState: dataTransfer_1.DataTransferState.Finished,
commands: [],
};
default:
// Abort the transfer.
this.abort(new Error(`Unknown error ${command.properties.errorCode}`));
return {
newState: dataTransfer_1.DataTransferState.Finished,
commands: [],
};
}
}
else if (command instanceof DataTransfer_1.DataTransferUploadContinueCommand) {
const result = {
newState: oldState,
commands: [],
};
// Atem requested more packets of data
if (oldState === dataTransfer_1.DataTransferState.Ready) {
// First bunch of packets, also send the description
result.newState = dataTransfer_1.DataTransferState.Transferring;
result.commands.push(this.generateDescriptionCommand(command.properties.transferId));
}
const nextChunks = this.getNextChunks(command.properties);
result.commands.push(...nextChunks);
// if (nextChunks.length === 0) this.abort(new Error('Ran out of data'))
return result;
}
else if (command instanceof DataTransfer_1.DataTransferCompleteCommand) {
// Atem reports that it recieved everything
if (oldState === dataTransfer_1.DataTransferState.Transferring) {
this.resolvePromise();
return {
newState: dataTransfer_1.DataTransferState.Finished,
commands: [],
};
}
else {
return { newState: oldState, commands: [] };
}
}
else {
// Unknown command
return { newState: oldState, commands: [] };
}
}
getNextChunks(props) {
const commands = [];
// Take a little less because the atem does that?
// const chunkSize = props.chunkSize - 4
const chunkSize = Math.floor(props.chunkSize / 8) * 8;
for (let i = 0; i < props.chunkCount; i++) {
// Make sure the packet isn't empty
if (tslib_1.__classPrivateFieldGet(this, _DataTransferUploadBuffer_bytesSent, "f") >= this.data.length)
break;
// Make sure the packet doesn't end in the middle of a RLE block
let shortenBy = 0;
if (chunkSize + tslib_1.__classPrivateFieldGet(this, _DataTransferUploadBuffer_bytesSent, "f") > this.data.length) {
// The last chunk can't end with a RLE header
shortenBy = tslib_1.__classPrivateFieldGet(this, _DataTransferUploadBuffer_bytesSent, "f") + chunkSize - this.data.length;
}
else if (rle_1.RLE_HEADER === this.data.readBigUint64BE(tslib_1.__classPrivateFieldGet(this, _DataTransferUploadBuffer_bytesSent, "f") + chunkSize - 8)) {
// RLE header starts 8 bytes before the end
shortenBy = 8;
}
else if (rle_1.RLE_HEADER === this.data.readBigUint64BE(tslib_1.__classPrivateFieldGet(this, _DataTransferUploadBuffer_bytesSent, "f") + chunkSize - 16)) {
// RLE header starts 16 bytes before the end
shortenBy = 16;
}
commands.push(new DataTransfer_1.DataTransferDataCommand({
transferId: props.transferId,
body: this.data.slice(tslib_1.__classPrivateFieldGet(this, _DataTransferUploadBuffer_bytesSent, "f"), tslib_1.__classPrivateFieldGet(this, _DataTransferUploadBuffer_bytesSent, "f") + chunkSize - shortenBy),
}));
tslib_1.__classPrivateFieldSet(this, _DataTransferUploadBuffer_bytesSent, tslib_1.__classPrivateFieldGet(this, _DataTransferUploadBuffer_bytesSent, "f") + (chunkSize - shortenBy), "f");
}
debug(`Generated ${commands.length} chunks for size ${chunkSize}`);
return commands;
}
}
exports.DataTransferUploadBuffer = DataTransferUploadBuffer;
_DataTransferUploadBuffer_bytesSent = new WeakMap();
//# sourceMappingURL=dataTransferUploadBuffer.js.map