timeline-state-resolver
Version:
Have timeline, control stuff
153 lines • 7.65 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.VMixXmlStateParser = void 0;
const xml = require("xml-js");
const vMixStateDiffer_1 = require("./vMixStateDiffer");
const timeline_state_resolver_types_1 = require("timeline-state-resolver-types");
/**
* Parses the state incoming from vMix into a TSR representation
*/
class VMixXmlStateParser {
parseVMixState(responseBody) {
const preParsed = xml.xml2json(responseBody, { compact: true, spaces: 4 });
const xmlState = JSON.parse(preParsed);
let mixes = xmlState['vmix']['mix'];
mixes = Array.isArray(mixes) ? mixes : mixes ? [mixes] : [];
const existingInputs = {};
const existingInputsAudio = {};
const inputsAddedByUs = {};
const inputsAddedByUsAudio = {};
const inputKeysToNumbers = {};
for (const input of xmlState['vmix']['inputs']['input']) {
inputKeysToNumbers[input['_attributes']['key']] = Number(input['_attributes']['number']);
}
for (const input of xmlState['vmix']['inputs']['input']) {
const title = input['_attributes']['title'];
const inputNumber = Number(input['_attributes']['number']);
const isAddedByUs = title.startsWith(vMixStateDiffer_1.TSR_INPUT_PREFIX);
let fixedListFilePaths = undefined;
if (input['_attributes']['type'] === 'VideoList' && input['list']['item'] != null) {
fixedListFilePaths = this.ensureArray(input['list']['item']).map((item) => item['_text']);
}
const layers = {};
if (input['overlay'] != null) {
this.ensureArray(input['overlay']).forEach((item) => {
const position = item['position']?.['_attributes'];
const crop = item['crop']?.['_attributes'];
layers[parseInt(item['_attributes']['index'], 10) + 1] = {
input: inputKeysToNumbers[item['_attributes']['key']],
zoom: Number(position?.['zoomX'] ?? 1),
panX: Number(position?.['panX'] ?? 0),
panY: Number(position?.['panY'] ?? 0),
cropLeft: Number(crop?.['X1'] ?? 0),
cropTop: Number(crop?.['Y1'] ?? 0),
cropRight: Number(crop?.['X2'] ?? 1),
cropBottom: Number(crop?.['Y2'] ?? 1),
};
});
}
let text = undefined;
if (input['text'] != null) {
this.ensureArray(input['text']).forEach((item) => {
text = text ?? {};
text[item['_attributes']['name']] = item['_text'];
});
}
let images = undefined;
if (input['image'] != null) {
this.ensureArray(input['image']).forEach((item) => {
images ?? (images = {});
images[item['_attributes']['name']] = item['_text'];
});
}
const result = {
number: inputNumber,
type: input['_attributes']['type'],
name: isAddedByUs ? title : undefined,
state: input['_attributes']['state'],
playing: input['_attributes']['state'] === 'Running',
position: Number(input['_attributes']['position']) || 0,
duration: Number(input['_attributes']['duration']) || 0,
loop: input['_attributes']['loop'] !== 'False',
transform: {
panX: Number(input['position']?.['_attributes']['panX'] ?? 0),
panY: Number(input['position']?.['_attributes']['panY'] ?? 0),
alpha: -1,
zoom: Number(input['position']?.['_attributes']['zoomX'] ?? 1), // assume that zoomX==zoomY
},
layers,
listFilePaths: fixedListFilePaths,
text,
images,
};
const resultAudio = {
muted: input['_attributes']['muted'] !== 'False',
volume: Number(input['_attributes']['volume'] || 100),
balance: Number(input['_attributes']['balance'] || 0),
solo: input['_attributes']['loop'] !== 'False',
audioBuses: input['_attributes']['audiobusses'],
};
if (isAddedByUs) {
inputsAddedByUs[title] = result;
inputsAddedByUsAudio[title] = resultAudio;
}
else {
existingInputs[inputNumber] = result;
existingInputsAudio[inputNumber] = resultAudio;
// TODO: how about we insert those under their titles too? That should partially lift the limitation of not being able to mix string and number input indexes
}
}
// For what lies ahead I apologise - Tom
return {
version: xmlState['vmix']['version']['_text'],
edition: xmlState['vmix']['edition']['_text'],
existingInputs,
existingInputsAudio,
inputsAddedByUs,
inputsAddedByUsAudio,
overlays: xmlState['vmix']['overlays']['overlay'].map((overlay) => {
return {
number: Number(overlay['_attributes']['number']),
input: overlay['_text'],
};
}),
mixes: [
{
number: 1,
program: Number(xmlState['vmix']['active']['_text']),
preview: Number(xmlState['vmix']['preview']['_text']),
transition: { effect: timeline_state_resolver_types_1.VMixTransitionType.Cut, duration: 0 },
},
...mixes.map((mix) => {
return {
number: Number(mix['_attributes']['number']),
program: Number(mix['active']['_text']),
preview: Number(mix['preview']['_text']),
transition: { effect: timeline_state_resolver_types_1.VMixTransitionType.Cut, duration: 0 },
};
}),
],
fadeToBlack: xmlState['vmix']['fadeToBlack']['_text'] === 'True',
recording: xmlState['vmix']['recording']['_text'] === 'True',
external: xmlState['vmix']['external']['_text'] === 'True',
streaming: xmlState['vmix']['streaming']['_text'] === 'True',
playlist: xmlState['vmix']['playList']['_text'] === 'True',
multiCorder: xmlState['vmix']['multiCorder']['_text'] === 'True',
fullscreen: xmlState['vmix']['fullscreen']['_text'] === 'True',
audio: [
{
volume: Number(xmlState['vmix']['audio']['master']['_attributes']['volume']),
muted: xmlState['vmix']['audio']['master']['_attributes']['muted'] === 'True',
meterF1: Number(xmlState['vmix']['audio']['master']['_attributes']['meterF1']),
meterF2: Number(xmlState['vmix']['audio']['master']['_attributes']['meterF2']),
headphonesVolume: Number(xmlState['vmix']['audio']['master']['_attributes']['headphonesVolume']),
},
],
};
}
ensureArray(value) {
return Array.isArray(value) ? value : [value];
}
}
exports.VMixXmlStateParser = VMixXmlStateParser;
//# sourceMappingURL=vMixXmlStateParser.js.map