timeline-state-resolver
Version:
Have timeline, control stuff
395 lines • 18.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VMixCommandSender = exports.VMixConnection = exports.ResponseTypes = void 0;
const eventemitter3_1 = require("eventemitter3");
const net_1 = require("net");
const timeline_state_resolver_types_1 = require("timeline-state-resolver-types");
const vMixResponseStreamReader_1 = require("./vMixResponseStreamReader");
const VMIX_DEFAULT_TCP_PORT = 8099;
var ResponseTypes;
(function (ResponseTypes) {
ResponseTypes["Info"] = "INFO";
ResponseTypes["OK"] = "OK";
ResponseTypes["ClientError"] = "ERROR";
ResponseTypes["ServerError"] = "FAILED";
})(ResponseTypes = exports.ResponseTypes || (exports.ResponseTypes = {}));
class VMixConnection extends eventemitter3_1.EventEmitter {
constructor(host, port = VMIX_DEFAULT_TCP_PORT, autoConnect = false) {
super();
this.host = host;
this.port = port;
this._connected = false;
this._responseStreamReader = new vMixResponseStreamReader_1.VMixResponseStreamReader();
if (autoConnect)
this._setupSocket();
this._responseStreamReader.on('response', (response) => this.emit('data', response));
this._responseStreamReader.on('error', (error) => this.emit('error', error));
}
get connected() {
return this._connected;
}
connect(host, port) {
this.host = host ?? this.host;
this.port = host ? port ?? VMIX_DEFAULT_TCP_PORT : this.port;
this._socket?.end();
this._setupSocket();
}
disconnect() {
this._socket?.end();
}
async requestVMixState() {
return this._sendCommand('XML');
}
async sendCommandFunction(func, args) {
const inp = args.input !== undefined ? `&Input=${args.input}` : '';
const val = args.value !== undefined ? `&Value=${args.value}` : '';
const dur = args.duration !== undefined ? `&Duration=${args.duration}` : '';
const mix = args.mix !== undefined ? `&Mix=${args.mix}` : '';
const selectedName = args.selectedName !== undefined ? `&SelectedName=${args.selectedName}` : '';
const ext = args.extra !== undefined ? args.extra : '';
const queryString = `${inp}${val}${dur}${mix}${ext}${selectedName}`.slice(1); // remove the first &
let command = `FUNCTION ${func}`;
if (queryString) {
command += ` ${queryString}`;
}
// this.emit('debug', `Sending command: ${command}`)
return this._sendCommand(command);
}
async _sendCommand(cmd) {
return new Promise((resolve) => {
this._socket?.write(cmd + '\r\n', (err) => resolve(err));
});
}
_triggerReconnect() {
if (!this._reconnectTimeout) {
this._reconnectTimeout = setTimeout(() => {
this._reconnectTimeout = undefined;
if (!this._connected)
this._setupSocket();
}, 5000);
}
}
_setupSocket() {
if (this._socket) {
this._socket.removeAllListeners();
if (!this._socket.destroyed) {
this._socket.destroy();
}
}
this._socket = new net_1.Socket();
this._socket.setNoDelay(true);
this._socket.setEncoding('utf-8');
this._socket.on('data', (data) => {
if (typeof data !== 'string') {
// this is against the types, but according to the docs the data will be a string
// the problem of a character split into chunks in transit should be taken care of
// (https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_readable_setencoding_encoding)
throw new Error('Received a non-string even though encoding should have been set to utf-8');
}
this._responseStreamReader.processIncomingData(data);
});
this._socket.on('connect', () => {
this._setConnected(true);
this._responseStreamReader.reset();
});
this._socket.on('close', () => {
this._setConnected(false);
this._triggerReconnect();
});
this._socket.on('error', (e) => {
if (`${e}`.match(/ECONNREFUSED/)) {
// Unable to connect, no need to handle this error
this._setConnected(false);
}
else {
this.emit('error', e);
}
});
this._socket.connect(this.port, this.host);
}
_setConnected(connected) {
if (connected) {
if (!this._connected) {
this._connected = true;
this.emit('connected');
}
}
else if (this._connected) {
this._connected = false;
this.emit('disconnected');
}
}
}
exports.VMixConnection = VMixConnection;
class VMixCommandSender {
constructor(vMixConnection) {
this.vMixConnection = vMixConnection;
}
async sendCommand(command) {
switch (command.command) {
case timeline_state_resolver_types_1.VMixCommand.PREVIEW_INPUT:
return this.setPreviewInput(command.input, command.mix);
case timeline_state_resolver_types_1.VMixCommand.TRANSITION:
return this.transition(command.input, command.effect, command.duration, command.mix);
case timeline_state_resolver_types_1.VMixCommand.AUDIO_VOLUME:
return this.setAudioLevel(command.input, command.value, command.fade);
case timeline_state_resolver_types_1.VMixCommand.AUDIO_BALANCE:
return this.setAudioBalance(command.input, command.value);
case timeline_state_resolver_types_1.VMixCommand.AUDIO_ON:
return this.setAudioOn(command.input);
case timeline_state_resolver_types_1.VMixCommand.AUDIO_OFF:
return this.setAudioOff(command.input);
case timeline_state_resolver_types_1.VMixCommand.AUDIO_AUTO_ON:
return this.setAudioAutoOn(command.input);
case timeline_state_resolver_types_1.VMixCommand.AUDIO_AUTO_OFF:
return this.setAudioAutoOff(command.input);
case timeline_state_resolver_types_1.VMixCommand.AUDIO_BUS_ON:
return this.setAudioBusOn(command.input, command.value);
case timeline_state_resolver_types_1.VMixCommand.AUDIO_BUS_OFF:
return this.setAudioBusOff(command.input, command.value);
case timeline_state_resolver_types_1.VMixCommand.FADER:
return this.setFader(command.value);
case timeline_state_resolver_types_1.VMixCommand.START_RECORDING:
return this.startRecording();
case timeline_state_resolver_types_1.VMixCommand.STOP_RECORDING:
return this.stopRecording();
case timeline_state_resolver_types_1.VMixCommand.START_STREAMING:
return this.startStreaming();
case timeline_state_resolver_types_1.VMixCommand.STOP_STREAMING:
return this.stopStreaming();
case timeline_state_resolver_types_1.VMixCommand.FADE_TO_BLACK:
return this.fadeToBlack();
case timeline_state_resolver_types_1.VMixCommand.ADD_INPUT:
return this.addInput(command.value);
case timeline_state_resolver_types_1.VMixCommand.REMOVE_INPUT:
return this.removeInput(command.input);
case timeline_state_resolver_types_1.VMixCommand.PLAY_INPUT:
return this.playInput(command.input);
case timeline_state_resolver_types_1.VMixCommand.PAUSE_INPUT:
return this.pauseInput(command.input);
case timeline_state_resolver_types_1.VMixCommand.SET_POSITION:
return this.setPosition(command.input, command.value);
case timeline_state_resolver_types_1.VMixCommand.SET_PAN_X:
return this.setPanX(command.input, command.value);
case timeline_state_resolver_types_1.VMixCommand.SET_PAN_Y:
return this.setPanY(command.input, command.value);
case timeline_state_resolver_types_1.VMixCommand.SET_ZOOM:
return this.setZoom(command.input, command.value);
case timeline_state_resolver_types_1.VMixCommand.SET_ALPHA:
return this.setAlpha(command.input, command.value);
case timeline_state_resolver_types_1.VMixCommand.LOOP_ON:
return this.loopOn(command.input);
case timeline_state_resolver_types_1.VMixCommand.LOOP_OFF:
return this.loopOff(command.input);
case timeline_state_resolver_types_1.VMixCommand.SET_INPUT_NAME:
return this.setInputName(command.input, command.value);
case timeline_state_resolver_types_1.VMixCommand.SET_OUPUT:
return this.setOutput(command.name, command.value, command.input);
case timeline_state_resolver_types_1.VMixCommand.START_EXTERNAL:
return this.startExternal();
case timeline_state_resolver_types_1.VMixCommand.STOP_EXTERNAL:
return this.stopExternal();
case timeline_state_resolver_types_1.VMixCommand.OVERLAY_INPUT_IN:
return this.overlayInputIn(command.value, command.input);
case timeline_state_resolver_types_1.VMixCommand.OVERLAY_INPUT_OUT:
return this.overlayInputOut(command.value);
case timeline_state_resolver_types_1.VMixCommand.SET_LAYER_INPUT:
return this.setLayerInput(command.input, command.index, command.value);
case timeline_state_resolver_types_1.VMixCommand.SET_LAYER_CROP:
return this.setLayerCrop(command.input, command.index, command.cropLeft, command.cropTop, command.cropRight, command.cropBottom);
case timeline_state_resolver_types_1.VMixCommand.SET_LAYER_PAN_X:
return this.setLayerPanX(command.input, command.index, command.value);
case timeline_state_resolver_types_1.VMixCommand.SET_LAYER_PAN_Y:
return this.setLayerPanY(command.input, command.index, command.value);
case timeline_state_resolver_types_1.VMixCommand.SET_LAYER_ZOOM:
return this.setLayerZoom(command.input, command.index, command.value);
case timeline_state_resolver_types_1.VMixCommand.SCRIPT_START:
return this.scriptStart(command.value);
case timeline_state_resolver_types_1.VMixCommand.SCRIPT_STOP:
return this.scriptStop(command.value);
case timeline_state_resolver_types_1.VMixCommand.SCRIPT_STOP_ALL:
return this.scriptStopAll();
case timeline_state_resolver_types_1.VMixCommand.LIST_ADD:
return this.listAdd(command.input, command.value);
case timeline_state_resolver_types_1.VMixCommand.LIST_REMOVE_ALL:
return this.listRemoveAll(command.input);
case timeline_state_resolver_types_1.VMixCommand.RESTART_INPUT:
return this.restart(command.input);
case timeline_state_resolver_types_1.VMixCommand.SET_TEXT:
return this.setText(command.input, command.value, command.fieldName);
case timeline_state_resolver_types_1.VMixCommand.BROWSER_NAVIGATE:
return this.browserNavigate(command.input, command.value);
case timeline_state_resolver_types_1.VMixCommand.SELECT_INDEX:
return this.selectIndex(command.input, command.value);
case timeline_state_resolver_types_1.VMixCommand.SET_IMAGE:
return this.setImage(command.input, command.value, command.fieldName);
default:
throw new Error(`vmixAPI: Command ${(command || {}).command} not implemented`);
}
}
async setPreviewInput(input, mix) {
return this.sendCommandFunction('PreviewInput', { input, mix });
}
async transition(input, effect, duration, mix) {
return this.sendCommandFunction(effect, { input, duration, mix });
}
async setAudioLevel(input, volume, fade) {
let value = Math.min(Math.max(volume, 0), 100).toString();
if (fade) {
value += ',' + fade.toString();
}
return this.sendCommandFunction(`SetVolume${fade ? 'Fade' : ''}`, { input: input, value });
}
async setAudioBalance(input, balance) {
return this.sendCommandFunction(`SetBalance`, { input, value: Math.min(Math.max(balance, -1), 1) });
}
async setAudioOn(input) {
return this.sendCommandFunction(`AudioOn`, { input });
}
async setAudioOff(input) {
return this.sendCommandFunction(`AudioOff`, { input });
}
async setAudioAutoOn(input) {
return this.sendCommandFunction(`AudioAutoOn`, { input });
}
async setAudioAutoOff(input) {
return this.sendCommandFunction(`AudioAutoOff`, { input });
}
async setAudioBusOn(input, value) {
return this.sendCommandFunction(`AudioBusOn`, { input, value });
}
async setAudioBusOff(input, value) {
return this.sendCommandFunction(`AudioBusOff`, { input, value });
}
async setFader(position) {
return this.sendCommandFunction(`SetFader`, { value: Math.min(Math.max(position, 0), 255) });
}
async setPanX(input, value) {
return this.sendCommandFunction(`SetPanX`, { input, value: Math.min(Math.max(value, -2), 2) });
}
async setPanY(input, value) {
return this.sendCommandFunction(`SetPanY`, { input, value: Math.min(Math.max(value, -2), 2) });
}
async setZoom(input, value) {
return this.sendCommandFunction(`SetZoom`, { input, value: Math.min(Math.max(value, 0), 5) });
}
async setAlpha(input, value) {
return this.sendCommandFunction(`SetAlpha`, { input, value: Math.min(Math.max(value, 0), 255) });
}
async startRecording() {
return this.sendCommandFunction(`StartRecording`, {});
}
async stopRecording() {
return this.sendCommandFunction(`StopRecording`, {});
}
async startStreaming() {
return this.sendCommandFunction(`StartStreaming`, {});
}
async stopStreaming() {
return this.sendCommandFunction(`StopStreaming`, {});
}
async fadeToBlack() {
return this.sendCommandFunction(`FadeToBlack`, {});
}
async addInput(file) {
return this.sendCommandFunction(`AddInput`, { value: file });
}
async removeInput(name) {
return this.sendCommandFunction(`RemoveInput`, { input: name });
}
async playInput(input) {
return this.sendCommandFunction(`Play`, { input: input });
}
async pauseInput(input) {
return this.sendCommandFunction(`Pause`, { input: input });
}
async setPosition(input, value) {
return this.sendCommandFunction(`SetPosition`, { input: input, value: value });
}
async loopOn(input) {
return this.sendCommandFunction(`LoopOn`, { input: input });
}
async loopOff(input) {
return this.sendCommandFunction(`LoopOff`, { input: input });
}
async setInputName(input, value) {
return this.sendCommandFunction(`SetInputName`, { input: input, value: value });
}
async setOutput(name, value, input) {
return this.sendCommandFunction(`SetOutput${name}`, { value, input });
}
async startExternal() {
return this.sendCommandFunction(`StartExternal`, {});
}
async stopExternal() {
return this.sendCommandFunction(`StopExternal`, {});
}
async overlayInputIn(name, input) {
return this.sendCommandFunction(`OverlayInput${name}In`, { input: input });
}
async overlayInputOut(name) {
return this.sendCommandFunction(`OverlayInput${name}Out`, {});
}
async setLayerInput(input, index, value) {
const val = `${index},${value}`;
// note: this could probably be replaced by SetLayer, but let's keep it backwards compatible until SetMultiViewOverlay becomes deprecated
return this.sendCommandFunction(`SetMultiViewOverlay`, { input, value: val });
}
async setLayerCrop(input, index, cropLeft, cropTop, cropRight, cropBottom) {
const value = `${cropLeft},${cropTop},${cropRight},${cropBottom}`;
return this.sendCommandFunction(`SetLayer${index}Crop`, { input, value });
}
async setLayerZoom(input, index, value) {
return this.sendCommandFunction(`SetLayer${index}Zoom`, { input, value });
}
async setLayerPanX(input, index, value) {
return this.sendCommandFunction(`SetLayer${index}PanX`, { input, value });
}
async setLayerPanY(input, index, value) {
return this.sendCommandFunction(`SetLayer${index}PanY`, { input, value });
}
async scriptStart(value) {
return this.sendCommandFunction(`ScriptStart`, { value });
}
async scriptStop(value) {
return this.sendCommandFunction(`ScriptStop`, { value });
}
async scriptStopAll() {
return this.sendCommandFunction(`ScriptStopAll`, {});
}
async lastPreset() {
return this.sendCommandFunction('LastPreset', {});
}
async openPreset(file) {
return this.sendCommandFunction('OpenPreset', { value: file });
}
async savePreset(file) {
return this.sendCommandFunction('SavePreset', { value: file });
}
async listAdd(input, value) {
return this.sendCommandFunction(`ListAdd`, { input, value: encodeURIComponent(value) });
}
async listRemoveAll(input) {
return this.sendCommandFunction(`ListRemoveAll`, { input });
}
async restart(input) {
return this.sendCommandFunction(`Restart`, { input });
}
async setText(input, value, fieldName) {
return this.sendCommandFunction(`SetText`, { input, value, selectedName: fieldName });
}
async browserNavigate(input, value) {
return this.sendCommandFunction(`BrowserNavigate`, { input, value: encodeURIComponent(value) });
}
async selectIndex(input, value) {
return this.sendCommandFunction(`SelectIndex`, { input, value });
}
async setImage(input, value, fieldName) {
return this.sendCommandFunction(`SetImage`, { input, value: encodeURIComponent(value), selectedName: fieldName });
}
async sendCommandFunction(func, args) {
return this.vMixConnection.sendCommandFunction(func, args);
}
}
exports.VMixCommandSender = VMixCommandSender;
//# sourceMappingURL=connection.js.map