atem-connection
Version:
Typescript Node.js library for connecting with an ATEM switcher.
923 lines • 41.2 kB
JavaScript
;
var _Atem_multiviewerFontFace, _Atem_multiviewerFontScale;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Atem = exports.BasicAtem = exports.DEFAULT_MAX_PACKET_SIZE = exports.DEFAULT_PORT = exports.AtemConnectionStatus = void 0;
const tslib_1 = require("tslib");
const eventemitter3_1 = require("eventemitter3");
const state_1 = require("./state");
const atemSocket_1 = require("./lib/atemSocket");
const Commands = require("./commands");
const DataTransferCommands = require("./commands/DataTransfer");
const DT = require("./dataTransfer");
const Util = require("./lib/atemUtil");
const videoMode_1 = require("./lib/videoMode");
const Enums = require("./enums");
const tally_1 = require("./lib/tally");
const multiviewLabel_1 = require("./lib/multiviewLabel");
const PLazy = require("p-lazy");
const commands_1 = require("./commands");
const dataTransferUploadBuffer_1 = require("./dataTransfer/dataTransferUploadBuffer");
const wavAudio_1 = require("./lib/converters/wavAudio");
const rle_1 = require("./lib/converters/rle");
const yuv422ToRgba_1 = require("./lib/converters/yuv422ToRgba");
var AtemConnectionStatus;
(function (AtemConnectionStatus) {
AtemConnectionStatus[AtemConnectionStatus["CLOSED"] = 0] = "CLOSED";
AtemConnectionStatus[AtemConnectionStatus["CONNECTING"] = 1] = "CONNECTING";
AtemConnectionStatus[AtemConnectionStatus["CONNECTED"] = 2] = "CONNECTED";
})(AtemConnectionStatus = exports.AtemConnectionStatus || (exports.AtemConnectionStatus = {}));
exports.DEFAULT_PORT = 9910;
exports.DEFAULT_MAX_PACKET_SIZE = 1416; // Matching ATEM software
class BasicAtem extends eventemitter3_1.EventEmitter {
constructor(options) {
super();
this._sentQueue = {};
this._state = state_1.AtemStateUtil.Create();
this._status = AtemConnectionStatus.CLOSED;
this.socket = new atemSocket_1.AtemSocket({
debugBuffers: options?.debugBuffers ?? false,
address: options?.address || '',
port: options?.port || exports.DEFAULT_PORT,
disableMultithreaded: options?.disableMultithreaded ?? false,
childProcessTimeout: options?.childProcessTimeout || 600,
maxPacketSize: options?.maxPacketSize ?? exports.DEFAULT_MAX_PACKET_SIZE,
});
this.dataTransferManager = new DT.DataTransferManager(this.sendCommands.bind(this));
this.socket.on('receivedCommands', (commands) => {
this.emit('receivedCommands', commands);
this._mutateState(commands);
});
this.socket.on('ackPackets', (trackingIds) => this._resolveCommands(trackingIds));
this.socket.on('info', (msg) => this.emit('info', msg));
this.socket.on('debug', (msg) => this.emit('debug', msg));
this.socket.on('error', (e) => this.emit('error', e));
this.socket.on('disconnect', () => {
this._status = AtemConnectionStatus.CLOSED;
this.dataTransferManager.stopCommandSending();
this._rejectAllCommands();
this.emit('disconnected');
this._state = undefined;
});
}
_onInitComplete() {
this.dataTransferManager.startCommandSending();
this.emit('connected');
}
get status() {
return this._status;
}
get state() {
return this._state;
}
/**
* Get the current videomode of the ATEM, if known
*/
get videoMode() {
if (!this.state)
return undefined;
return (0, videoMode_1.getVideoModeInfo)(this.state.settings.videoMode);
}
async connect(address, port) {
return this.socket.connect(address, port);
}
async disconnect() {
return this.socket.disconnect();
}
async destroy() {
this.dataTransferManager.stopCommandSending();
return this.socket.destroy();
}
async sendCommands(commands) {
const trackingIds = await this.socket.sendCommands(commands);
const promises = [];
for (const trackingId of trackingIds) {
promises.push(new Promise((resolve, reject) => {
this._sentQueue[trackingId] = {
resolve,
reject,
};
}));
}
await Promise.allSettled(promises);
}
async sendCommand(command) {
return await this.sendCommands([command]);
}
_mutateState(commands) {
// Is this the start of a new connection?
if (commands.find((cmd) => cmd instanceof Commands.VersionCommand)) {
// On start of connection, create a new state object
this._state = state_1.AtemStateUtil.Create();
this._status = AtemConnectionStatus.CONNECTING;
}
const allChangedPaths = [];
const state = this._state;
for (const command of commands) {
if (command instanceof commands_1.TimeCommand) {
this.emit('updatedTime', command.properties);
}
else if (command instanceof Commands.FairlightMixerMasterLevelsUpdateCommand) {
this.emit('levelChanged', {
system: 'fairlight',
type: 'master',
levels: command.properties,
});
}
else if (command instanceof Commands.FairlightMixerSourceLevelsUpdateCommand) {
this.emit('levelChanged', {
system: 'fairlight',
type: 'source',
source: command.source,
index: command.index,
levels: command.properties,
});
}
else if (state) {
try {
const changePaths = command.applyToState(state);
if (!Array.isArray(changePaths)) {
allChangedPaths.push(changePaths);
}
else {
allChangedPaths.push(...changePaths);
}
}
catch (e) {
if (e instanceof state_1.InvalidIdError) {
this.emit('debug', `Invalid command id: ${e}. Command: ${command.constructor.name} ${Util.commandStringify(command)}`);
}
else {
this.emit('error', `MutateState failed: ${e}. Command: ${command.constructor.name} ${Util.commandStringify(command)}`);
}
}
}
// TODO - this isn't very efficient, but it works
for (const dtCommand of Object.values(DataTransferCommands)) {
if (typeof dtCommand === 'function' && command instanceof dtCommand) {
this.dataTransferManager.queueHandleCommand(command);
}
}
}
const initComplete = commands.find((cmd) => cmd instanceof Commands.InitCompleteCommand);
if (initComplete) {
this._status = AtemConnectionStatus.CONNECTED;
this._onInitComplete();
}
else if (state && this._status === AtemConnectionStatus.CONNECTED && allChangedPaths.length > 0) {
this.emit('stateChanged', state, allChangedPaths);
}
}
_resolveCommands(trackingIds) {
trackingIds.forEach((trackingId) => {
const sent = this._sentQueue[trackingId];
if (sent) {
sent.resolve();
delete this._sentQueue[trackingId];
}
});
}
_rejectAllCommands() {
// Take a copy in case the promises cause more mutations
const sentQueue = this._sentQueue;
this._sentQueue = {};
Object.values(sentQueue).forEach((sent) => sent.reject());
}
}
exports.BasicAtem = BasicAtem;
class Atem extends BasicAtem {
constructor(options) {
super(options);
_Atem_multiviewerFontFace.set(this, void 0);
_Atem_multiviewerFontScale.set(this, void 0);
tslib_1.__classPrivateFieldSet(this, _Atem_multiviewerFontFace, PLazy.from(async () => (0, multiviewLabel_1.loadFont)()), "f");
tslib_1.__classPrivateFieldSet(this, _Atem_multiviewerFontScale, 1.0, "f");
}
/**
* Set the font to use for the multiviewer, or reset to default
*/
async setMultiviewerFontFace(font) {
let loadedFont;
if (font) {
if (typeof font === 'string') {
loadedFont = await (0, multiviewLabel_1.loadFont)(font);
}
else {
loadedFont = font;
}
}
else {
loadedFont = await (0, multiviewLabel_1.loadFont)();
}
tslib_1.__classPrivateFieldSet(this, _Atem_multiviewerFontFace, Promise.resolve(loadedFont), "f");
}
/**
* Set the scale factor for the multiviewer text. Default is 1
*/
setMultiviewerFontScale(scale) {
if (typeof scale === 'number') {
if (scale <= 0)
throw new Error('Scale must be greater than 0');
tslib_1.__classPrivateFieldSet(this, _Atem_multiviewerFontScale, scale, "f");
}
else if (scale === null) {
tslib_1.__classPrivateFieldSet(this, _Atem_multiviewerFontScale, 1.0, "f");
}
}
async changeProgramInput(input, me = 0) {
const command = new Commands.ProgramInputCommand(me, input);
return this.sendCommand(command);
}
async changePreviewInput(input, me = 0) {
const command = new Commands.PreviewInputCommand(me, input);
return this.sendCommand(command);
}
async cut(me = 0) {
const command = new Commands.CutCommand(me);
return this.sendCommand(command);
}
async autoTransition(me = 0) {
const command = new Commands.AutoTransitionCommand(me);
return this.sendCommand(command);
}
async fadeToBlack(me = 0) {
const command = new Commands.FadeToBlackAutoCommand(me);
return this.sendCommand(command);
}
async setFadeToBlackRate(rate, me = 0) {
const command = new Commands.FadeToBlackRateCommand(me, rate);
return this.sendCommand(command);
}
async autoDownstreamKey(key = 0, isTowardsOnAir) {
const command = new Commands.DownstreamKeyAutoCommand(key);
command.updateProps({ isTowardsOnAir });
return this.sendCommand(command);
}
async setDipTransitionSettings(newProps, me = 0) {
const command = new Commands.TransitionDipCommand(me);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setDVETransitionSettings(newProps, me = 0) {
const command = new Commands.TransitionDVECommand(me);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setMixTransitionSettings(newProps, me = 0) {
const command = new Commands.TransitionMixCommand(me, newProps.rate);
return this.sendCommand(command);
}
async setTransitionPosition(position, me = 0) {
const command = new Commands.TransitionPositionCommand(me, position);
return this.sendCommand(command);
}
async previewTransition(on, me = 0) {
const command = new Commands.PreviewTransitionCommand(me, on);
return this.sendCommand(command);
}
async setTransitionStyle(newProps, me = 0) {
const command = new Commands.TransitionPropertiesCommand(me);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setStingerTransitionSettings(newProps, me = 0) {
const command = new Commands.TransitionStingerCommand(me);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setWipeTransitionSettings(newProps, me = 0) {
const command = new Commands.TransitionWipeCommand(me);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setAuxSource(source, bus = 0) {
const command = new Commands.AuxSourceCommand(bus, source);
return this.sendCommand(command);
}
async setDownstreamKeyTie(tie, key = 0) {
const command = new Commands.DownstreamKeyTieCommand(key, tie);
return this.sendCommand(command);
}
async setDownstreamKeyOnAir(onAir, key = 0) {
const command = new Commands.DownstreamKeyOnAirCommand(key, onAir);
return this.sendCommand(command);
}
async setDownstreamKeyCutSource(input, key = 0) {
const command = new Commands.DownstreamKeyCutSourceCommand(key, input);
return this.sendCommand(command);
}
async setDownstreamKeyFillSource(input, key = 0) {
const command = new Commands.DownstreamKeyFillSourceCommand(key, input);
return this.sendCommand(command);
}
async setDownstreamKeyGeneralProperties(props, key = 0) {
const command = new Commands.DownstreamKeyGeneralCommand(key);
command.updateProps(props);
return this.sendCommand(command);
}
async setDownstreamKeyMaskSettings(props, key = 0) {
const command = new Commands.DownstreamKeyMaskCommand(key);
command.updateProps(props);
return this.sendCommand(command);
}
async setDownstreamKeyRate(rate, key = 0) {
const command = new Commands.DownstreamKeyRateCommand(key, rate);
return this.sendCommand(command);
}
async setTime(hour, minute, second, frame) {
const command = new Commands.TimeCommand({ hour, minute, second, frame });
return this.sendCommand(command);
}
async setTimeMode(mode) {
const command = new Commands.TimeConfigCommand(mode);
return this.sendCommand(command);
}
async requestTime() {
const command = new Commands.TimeRequestCommand();
return this.sendCommand(command);
}
async macroContinue() {
const command = new Commands.MacroActionCommand(0, Enums.MacroAction.Continue);
return this.sendCommand(command);
}
async macroDelete(index = 0) {
const command = new Commands.MacroActionCommand(index, Enums.MacroAction.Delete);
return this.sendCommand(command);
}
async macroInsertUserWait() {
const command = new Commands.MacroActionCommand(0, Enums.MacroAction.InsertUserWait);
return this.sendCommand(command);
}
async macroInsertTimedWait(frames) {
const command = new Commands.MacroAddTimedPauseCommand(frames);
return this.sendCommand(command);
}
async macroRun(index = 0) {
const command = new Commands.MacroActionCommand(index, Enums.MacroAction.Run);
return this.sendCommand(command);
}
async macroStop() {
const command = new Commands.MacroActionCommand(0, Enums.MacroAction.Stop);
return this.sendCommand(command);
}
async macroStartRecord(index, name, description) {
const command = new Commands.MacroRecordCommand(index, name, description);
return this.sendCommand(command);
}
async macroStopRecord() {
const command = new Commands.MacroActionCommand(0, Enums.MacroAction.StopRecord);
return this.sendCommand(command);
}
async macroUpdateProperties(props, index = 0) {
const command = new Commands.MacroPropertiesCommand(index);
command.updateProps(props);
return this.sendCommand(command);
}
async macroSetLoop(loop) {
const command = new Commands.MacroRunStatusCommand();
command.updateProps({ loop });
return this.sendCommand(command);
}
async downloadMacro(index) {
return this.dataTransferManager.downloadMacro(index);
}
async uploadMacro(index, name, data) {
return this.dataTransferManager.uploadMacro(index, data, name);
}
async setMultiViewerWindowSource(source, mv = 0, window = 0) {
const command = new Commands.MultiViewerSourceCommand(mv, window, source);
return this.sendCommand(command);
}
async setMultiViewerWindowSafeAreaEnabled(safeAreaEnabled, mv = 0, window = 0) {
const command = new Commands.MultiViewerWindowSafeAreaCommand(mv, window, safeAreaEnabled);
return this.sendCommand(command);
}
async setMultiViewerWindowVuEnabled(vuEnabled, mv = 0, window = 0) {
const command = new Commands.MultiViewerWindowVuMeterCommand(mv, window, vuEnabled);
return this.sendCommand(command);
}
async setMultiViewerVuOpacity(opacity, mv = 0) {
const command = new Commands.MultiViewerVuOpacityCommand(mv, opacity);
return this.sendCommand(command);
}
async setMultiViewerProperties(props, mv = 0) {
const command = new Commands.MultiViewerPropertiesCommand(mv);
command.updateProps(props);
return this.sendCommand(command);
}
async setColorGeneratorColour(newProps, index = 0) {
const command = new Commands.ColorGeneratorCommand(index);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setMediaPlayerSettings(newProps, player = 0) {
const command = new Commands.MediaPlayerStatusCommand(player);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setMediaPlayerSource(newProps, player = 0) {
const command = new Commands.MediaPlayerSourceCommand(player);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setMediaClip(index, name, frames = 1) {
const command = new Commands.MediaPoolSetClipCommand({ index, name, frames });
return this.sendCommand(command);
}
async clearMediaPoolClip(clipId) {
const command = new Commands.MediaPoolClearClipCommand(clipId);
return this.sendCommand(command);
}
async clearMediaPoolStill(stillId) {
const command = new Commands.MediaPoolClearStillCommand(stillId);
return this.sendCommand(command);
}
async captureMediaPoolStill() {
const command = new Commands.MediaPoolCaptureStillCommand();
return this.sendCommand(command);
}
async setSuperSourceBoxSettings(newProps, box = 0, ssrcId = 0) {
const command = new Commands.SuperSourceBoxParametersCommand(ssrcId, box);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setSuperSourceProperties(newProps, ssrcId = 0) {
if (this.state && this.state.info.apiVersion >= Enums.ProtocolVersion.V8_0) {
const command = new Commands.SuperSourcePropertiesV8Command(ssrcId);
command.updateProps(newProps);
return this.sendCommand(command);
}
else {
const command = new Commands.SuperSourcePropertiesCommand();
command.updateProps(newProps);
return this.sendCommand(command);
}
}
async setSuperSourceBorder(newProps, ssrcId = 0) {
if (this.state && this.state.info.apiVersion >= Enums.ProtocolVersion.V8_0) {
const command = new Commands.SuperSourceBorderCommand(ssrcId);
command.updateProps(newProps);
return this.sendCommand(command);
}
else {
const command = new Commands.SuperSourcePropertiesCommand();
command.updateProps(newProps);
return this.sendCommand(command);
}
}
async setInputSettings(newProps, input = 0) {
const command = new Commands.InputPropertiesCommand(input);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setUpstreamKeyerChromaSettings(newProps, me = 0, keyer = 0) {
const command = new Commands.MixEffectKeyChromaCommand(me, keyer);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setUpstreamKeyerAdvancedChromaProperties(newProps, me = 0, keyer = 0) {
const command = new Commands.MixEffectKeyAdvancedChromaPropertiesCommand(me, keyer);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setUpstreamKeyerAdvancedChromaSample(newProps, me = 0, keyer = 0) {
const command = new Commands.MixEffectKeyAdvancedChromaSampleCommand(me, keyer);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setUpstreamKeyerAdvancedChromaSampleReset(flags, me = 0, keyer = 0) {
const command = new Commands.MixEffectKeyAdvancedChromaSampleResetCommand(me, keyer, flags);
return this.sendCommand(command);
}
async setUpstreamKeyerCutSource(cutSource, me = 0, keyer = 0) {
const command = new Commands.MixEffectKeyCutSourceSetCommand(me, keyer, cutSource);
return this.sendCommand(command);
}
async setUpstreamKeyerFillSource(fillSource, me = 0, keyer = 0) {
const command = new Commands.MixEffectKeyFillSourceSetCommand(me, keyer, fillSource);
return this.sendCommand(command);
}
async setUpstreamKeyerDVESettings(newProps, me = 0, keyer = 0) {
const command = new Commands.MixEffectKeyDVECommand(me, keyer);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setUpstreamKeyerLumaSettings(newProps, me = 0, keyer = 0) {
const command = new Commands.MixEffectKeyLumaCommand(me, keyer);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setUpstreamKeyerMaskSettings(newProps, me = 0, keyer = 0) {
const command = new Commands.MixEffectKeyMaskSetCommand(me, keyer);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setUpstreamKeyerPatternSettings(newProps, me = 0, keyer = 0) {
const command = new Commands.MixEffectKeyPatternCommand(me, keyer);
command.updateProps(newProps);
return this.sendCommand(command);
}
async setUpstreamKeyerOnAir(onAir, me = 0, keyer = 0) {
const command = new Commands.MixEffectKeyOnAirCommand(me, keyer, onAir);
return this.sendCommand(command);
}
async runUpstreamKeyerFlyKeyTo(mixEffect, upstreamKeyerId, keyFrameId) {
const command = new Commands.MixEffectKeyRunToCommand(mixEffect, upstreamKeyerId, keyFrameId, 0);
return this.sendCommand(command);
}
async runUpstreamKeyerFlyKeyToInfinite(mixEffect, upstreamKeyerId, direction) {
const command = new Commands.MixEffectKeyRunToCommand(mixEffect, upstreamKeyerId, Enums.FlyKeyKeyFrame.RunToInfinite, direction);
return this.sendCommand(command);
}
async storeUpstreamKeyerFlyKeyKeyframe(mixEffect, upstreamKeyerId, keyframe) {
const command = new Commands.MixEffectKeyFlyKeyframeStoreCommand(mixEffect, upstreamKeyerId, keyframe);
return this.sendCommand(command);
}
async setUpstreamKeyerFlyKeyKeyframe(mixEffect, upstreamKeyerId, keyframe, properties) {
const command = new Commands.MixEffectKeyFlyKeyframeCommand(mixEffect, upstreamKeyerId, keyframe);
command.updateProps(properties);
return this.sendCommand(command);
}
async setUpstreamKeyerType(newProps, me = 0, keyer = 0) {
const command = new Commands.MixEffectKeyTypeSetCommand(me, keyer);
command.updateProps(newProps);
return this.sendCommand(command);
}
/**
* Download a frame of a clip from the ATEM media pool
*
* Note: This performs colour conversions in JS, which is not very CPU efficient. If performance is important,
* consider using [@atem-connection/image-tools](https://www.npmjs.com/package/@atem-connection/image-tools) to
* pre-convert the images with more optimal algorithms
* @param clipIndex Clip index to download
* @param frameIndex Frame index to download from the clip
* @param format The pixel format to return for the downloaded image. 'raw' passes through unchanged, and will be RLE encoded.
* @returns Promise which returns the image once downloaded. If the still slot is not in use, this will throw
*/
async downloadClipFrame(clipIndex, frameIndex, format = 'rgba') {
let rawBuffer = await this.dataTransferManager.downloadClipFrame(clipIndex, frameIndex);
if (format === 'raw') {
return rawBuffer;
}
if (!this.state)
throw new Error('Unable to check current resolution');
const resolution = (0, videoMode_1.getVideoModeInfo)(this.state.settings.videoMode);
if (!resolution)
throw new Error('Failed to determine required resolution');
rawBuffer = (0, rle_1.decodeRLE)(rawBuffer, resolution.width * resolution.height * 4);
switch (format) {
case 'yuv':
return rawBuffer;
case 'rgba':
default:
return (0, yuv422ToRgba_1.convertYUV422ToRGBA)(resolution.width, resolution.height, rawBuffer);
}
}
/**
* Download a still image from the ATEM media pool
*
* Note: This performs colour conversions in JS, which is not very CPU efficient. If performance is important,
* consider using [@atem-connection/image-tools](https://www.npmjs.com/package/@atem-connection/image-tools) to
* pre-convert the images with more optimal algorithms
* @param index Still index to download
* @param format The pixel format to return for the downloaded image. 'raw' passes through unchanged, and will be RLE encoded.
* @returns Promise which returns the image once downloaded. If the still slot is not in use, this will throw
*/
async downloadStill(index, format = 'rgba') {
let rawBuffer = await this.dataTransferManager.downloadStill(index);
if (format === 'raw') {
return rawBuffer;
}
if (!this.state)
throw new Error('Unable to check current resolution');
const resolution = (0, videoMode_1.getVideoModeInfo)(this.state.settings.videoMode);
if (!resolution)
throw new Error('Failed to determine required resolution');
rawBuffer = (0, rle_1.decodeRLE)(rawBuffer, resolution.width * resolution.height * 4);
switch (format) {
case 'yuv':
return rawBuffer;
case 'rgba':
default:
return (0, yuv422ToRgba_1.convertYUV422ToRGBA)(resolution.width, resolution.height, rawBuffer);
}
}
/**
* Upload a still image to the ATEM media pool
*
* Note: This performs colour conversions in JS, which is not very CPU efficient. If performance is important,
* consider using [@atem-connection/image-tools](https://www.npmjs.com/package/@atem-connection/image-tools) to
* pre-convert the images with more optimal algorithms
* @param index Still index to upload to
* @param data a RGBA pixel buffer, or an already YUVA encoded image
* @param name Name to give the uploaded image
* @param description Description for the uploaded image
* @param options Upload options
* @returns Promise which resolves once the image is uploaded
*/
async uploadStill(index, data, name, description, options) {
if (!this.state)
throw new Error('Unable to check current resolution');
const resolution = (0, videoMode_1.getVideoModeInfo)(this.state.settings.videoMode);
if (!resolution)
throw new Error('Failed to determine required resolution');
const encodedData = (0, dataTransferUploadBuffer_1.generateUploadBufferInfo)(data, resolution, !options?.disableRLE);
return this.dataTransferManager.uploadStill(index, encodedData, name, description);
}
/**
* Upload a clip to the ATEM media pool
*
* Note: This performs colour conversions in JS, which is not very CPU efficient. If performance is important,
* consider using [@atem-connection/image-tools](https://www.npmjs.com/package/@atem-connection/image-tools) to
* pre-convert the images with more optimal algorithms
* @param index Clip index to upload to
* @param frames Array or generator of frames. Each frame can be a RGBA pixel buffer, or an already YUVA encoded image
* @param name Name to give the uploaded clip
* @param options Upload options
* @returns Promise which resolves once the clip is uploaded
*/
async uploadClip(index, frames, name, options) {
if (!this.state)
throw new Error('Unable to check current resolution');
const resolution = (0, videoMode_1.getVideoModeInfo)(this.state.settings.videoMode);
if (!resolution)
throw new Error('Failed to determine required resolution');
const provideFrame = async function* () {
for await (const frame of frames) {
yield (0, dataTransferUploadBuffer_1.generateUploadBufferInfo)(frame, resolution, !options?.disableRLE);
}
};
return this.dataTransferManager.uploadClip(index, provideFrame(), name);
}
/**
* Upload clip audio to the ATEM media pool
* @param index Clip index to upload to
* @param data stereo 48khz 24bit WAV audio data
* @param name Name to give the uploaded audio
* @returns Promise which resolves once the clip audio is uploaded
*/
async uploadAudio(index, data, name) {
return this.dataTransferManager.uploadAudio(index, (0, wavAudio_1.convertWAVToRaw)(data, this.state?.info?.model), name);
}
async setClassicAudioMixerInputProps(index, props) {
const command = new Commands.AudioMixerInputCommand(index);
command.updateProps(props);
return this.sendCommand(command);
}
async setClassicAudioMixerMasterProps(props) {
const command = new Commands.AudioMixerMasterCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setClassicAudioMixerMonitorProps(props) {
const command = new Commands.AudioMixerMonitorCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setClassicAudioMixerHeadphonesProps(props) {
const command = new Commands.AudioMixerHeadphonesCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setClassicAudioResetPeaks(props) {
const command = new Commands.AudioMixerResetPeaksCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setClassicAudioMixerProps(props) {
const command = new Commands.AudioMixerPropertiesCommand(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerMasterProps(props) {
const command = new Commands.FairlightMixerMasterCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerMasterCompressorProps(props) {
const command = new Commands.FairlightMixerMasterCompressorCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerMasterLimiterProps(props) {
const command = new Commands.FairlightMixerMasterLimiterCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerMasterEqualizerBandProps(band, props) {
const command = new Commands.FairlightMixerMasterEqualizerBandCommand(band);
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerMasterEqualizerReset(props) {
const command = new Commands.FairlightMixerMasterEqualizerResetCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerMasterDynamicsReset(props) {
const command = new Commands.FairlightMixerMasterDynamicsResetCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerResetPeaks(props) {
const command = new Commands.FairlightMixerResetPeakLevelsCommand(props);
return this.sendCommand(command);
}
async startFairlightMixerSendLevels() {
const command = new Commands.FairlightMixerSendLevelsCommand(true);
return this.sendCommand(command);
}
async stopFairlightMixerSendLevels() {
const command = new Commands.FairlightMixerSendLevelsCommand(false);
return this.sendCommand(command);
}
async setFairlightAudioMixerMonitorProps(props) {
const command = new Commands.FairlightMixerMonitorCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerMonitorSolo(props) {
const command = new Commands.FairlightMixerMonitorSoloCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerInputProps(index, props) {
if (this.state && this.state.info.apiVersion >= Enums.ProtocolVersion.V8_0) {
const command = new Commands.FairlightMixerInputV8Command(index);
command.updateProps(props);
return this.sendCommand(command);
}
else {
const command = new Commands.FairlightMixerInputCommand(index);
command.updateProps(props);
return this.sendCommand(command);
}
}
async setFairlightAudioMixerSourceProps(index, source, props) {
const command = new Commands.FairlightMixerSourceCommand(index, BigInt(source));
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerSourceCompressorProps(index, source, props) {
const command = new Commands.FairlightMixerSourceCompressorCommand(index, BigInt(source));
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerSourceLimiterProps(index, source, props) {
const command = new Commands.FairlightMixerSourceLimiterCommand(index, BigInt(source));
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerSourceExpanderProps(index, source, props) {
const command = new Commands.FairlightMixerSourceExpanderCommand(index, BigInt(source));
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerSourceEqualizerBandProps(index, source, band, props) {
const command = new Commands.FairlightMixerSourceEqualizerBandCommand(index, BigInt(source), band);
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerSourceDynamicsReset(index, source, props) {
const command = new Commands.FairlightMixerSourceDynamicsResetCommand(index, BigInt(source));
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerSourceEqualizerReset(index, source, props) {
const command = new Commands.FairlightMixerSourceEqualizerResetCommand(index, BigInt(source));
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioMixerSourceResetPeaks(index, source, props) {
const command = new Commands.FairlightMixerSourceResetPeakLevelsCommand(index, BigInt(source), props);
return this.sendCommand(command);
}
async startStreaming() {
const command = new Commands.StreamingStatusCommand(true);
return this.sendCommand(command);
}
async stopStreaming() {
const command = new Commands.StreamingStatusCommand(false);
return this.sendCommand(command);
}
async requestStreamingDuration() {
const command = new Commands.StreamingRequestDurationCommand();
return this.sendCommand(command);
}
async setStreamingService(props) {
const command = new Commands.StreamingServiceCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setStreamingAudioBitrates(lowBitrate, highBitrate) {
const command = new Commands.StreamingAudioBitratesCommand(lowBitrate, highBitrate);
return this.sendCommand(command);
}
async startRecording() {
const command = new Commands.RecordingStatusCommand(true);
return this.sendCommand(command);
}
async stopRecording() {
const command = new Commands.RecordingStatusCommand(false);
return this.sendCommand(command);
}
async requestRecordingDuration() {
const command = new Commands.RecordingRequestDurationCommand();
return this.sendCommand(command);
}
async switchRecordingDisk() {
const command = new Commands.RecordingRequestSwitchDiskCommand();
return this.sendCommand(command);
}
async setRecordingSettings(props) {
const command = new Commands.RecordingSettingsCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setEnableISORecording(enabled) {
const command = new Commands.RecordingISOCommand(enabled);
return this.sendCommand(command);
}
async saveStartupState() {
const command = new Commands.StartupStateSaveCommand();
return this.sendCommand(command);
}
async clearStartupState() {
const command = new Commands.StartupStateClearCommand();
return this.sendCommand(command);
}
listVisibleInputs(mode, me = 0) {
if (this.state) {
return (0, tally_1.listVisibleInputs)(mode, this.state, me);
}
else {
return [];
}
}
async setMediaPoolSettings(props) {
const command = new Commands.MediaPoolSettingsSetCommand(props.maxFrames);
return this.sendCommand(command);
}
async requestDisplayClockTime() {
const command = new Commands.DisplayClockRequestTimeCommand();
return this.sendCommand(command);
}
async setDisplayClockState(state) {
const command = new Commands.DisplayClockStateSetCommand(state);
return this.sendCommand(command);
}
async setDisplayClockProperties(props) {
const command = new Commands.DisplayClockPropertiesSetCommand();
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioRoutingSourceProperties(sourceId, props) {
const command = new Commands.AudioRoutingSourceCommand(sourceId);
command.updateProps(props);
return this.sendCommand(command);
}
async setFairlightAudioRoutingOutputProperties(sourceId, props) {
const command = new Commands.AudioRoutingOutputCommand(sourceId);
command.updateProps(props);
return this.sendCommand(command);
}
hasInternalMultiviewerLabelGeneration() {
return !!this.state && (0, multiviewLabel_1.hasInternalMultiviewerLabelGeneration)(this.state?.info.model);
}
/**
* Write a custom multiviewer label buffer
* @param inputId The input id
* @param buffer Label buffer
* @returns Promise that resolves once upload is complete
*/
async writeMultiviewerLabel(inputId, buffer) {
if (this.hasInternalMultiviewerLabelGeneration())
throw new Error(`ATEM doesn't support custom labels`);
// Verify the buffer doesnt contain data that is 'out of bounds' and will crash the atem
const badValues = new Set([255, 254]);
for (const val of buffer) {
if (badValues.has(val)) {
throw new Error(`Buffer contains invalid value ${val}`);
}
}
return this.dataTransferManager.uploadMultiViewerLabel(inputId, buffer);
}
/**
* Generate and upload a multiviewer label
* @param inputId The input id
* @param text Label text
* @returns Promise that resolves once upload is complete
*/
async drawMultiviewerLabel(inputId, text) {
if (this.hasInternalMultiviewerLabelGeneration())
throw new Error(`ATEM doesn't support custom labels`);
const props = (0, multiviewLabel_1.calculateGenerateMultiviewerLabelProps)(this.state ?? null);
if (!props)
throw new Error(`Failed to determine render properties`);
const fontFace = await tslib_1.__classPrivateFieldGet(this, _Atem_multiviewerFontFace, "f");
const buffer = (0, multiviewLabel_1.generateMultiviewerLabel)(fontFace, tslib_1.__classPrivateFieldGet(this, _Atem_multiviewerFontScale, "f"), text, props);
// Note: we should probably validate the buffer looks like it doesn't contain crashy data, but as we generate we can trust it
return this.dataTransferManager.uploadMultiViewerLabel(inputId, buffer);
}
}
exports.Atem = Atem;
_Atem_multiviewerFontFace = new WeakMap(), _Atem_multiviewerFontScale = new WeakMap();
//# sourceMappingURL=atem.js.map