atem-connection
Version:
Typescript Node.js library for connecting with an ATEM switcher.
195 lines • 8.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.listVisibleInputs = void 0;
const state_1 = require("../state");
const Enums = require("../enums");
/**
* Helper method used by listVisibleInputs.
* This got broken out into its own method because
* it gets called multiple times, and gets called in a loop.
* Breaking it out made listVisibleInputs much easier to read.
*/
function _calcActiveMeInputs(inputs, mode, state, meId) {
const meRef = state_1.AtemStateUtil.getMixEffect(state, meId);
if (mode === 'preview') {
if (meRef.transitionProperties.selection.includes(Enums.TransitionSelection.Background)) {
inputs.add(meRef.previewInput);
}
}
else {
inputs.add(meRef.programInput);
}
// Upstream Keyers
meRef.upstreamKeyers
.filter((usk) => {
if (usk) {
const keyerMask = 1 << (usk.upstreamKeyerId + 1);
const isPartOfTransition = meRef.transitionProperties.selection.includes(keyerMask);
if (mode === 'program') {
if (meRef.transitionPosition.inTransition) {
return usk.onAir || isPartOfTransition;
}
return usk.onAir;
}
return (isPartOfTransition && !usk.onAir) || (usk.onAir && !isPartOfTransition);
}
else {
return false;
}
})
.forEach((usk) => {
if (usk) {
inputs.add(usk.fillSource);
// This is the only USK type that actually uses the cutSource.
if (usk.mixEffectKeyType === Enums.MixEffectKeyType.Luma) {
inputs.add(usk.cutSource);
}
}
});
// DSKs only show up on ME 1,
// so we only add them if that's the ME we are currently processing.
if (meId === 0) {
state.video.downstreamKeyers
.filter((dsk) => {
if (dsk) {
if (mode === 'program') {
return dsk.onAir || dsk.inTransition;
}
if (!dsk.properties) {
// Data isn't hydrated yet, we'll get 'em next time.
return false;
}
return dsk.properties.tie && !dsk.onAir;
}
else {
return false;
}
})
.forEach((dsk) => {
if (dsk && dsk.sources) {
inputs.add(dsk.sources.fillSource);
inputs.add(dsk.sources.cutSource);
}
});
}
// Compute what sources are currently participating in a transition.
const isTransitionInProgram = mode === 'program' && meRef.transitionPosition.inTransition;
const isTransitionInPreview = mode === 'preview' && meRef.transitionPreview && meRef.transitionPosition.handlePosition > 0;
if (isTransitionInProgram || isTransitionInPreview) {
if (meRef.transitionProperties.selection.includes(Enums.TransitionSelection.Background)) {
// Includes background
inputs.add(meRef.previewInput);
}
// From here, what inputs are participating in the transition depends
// on the transition style being used, so we handle each separately.
switch (meRef.transitionProperties.style) {
case Enums.TransitionStyle.DIP:
if (meRef.transitionSettings.dip) {
inputs.add(meRef.transitionSettings.dip.input);
}
break;
case Enums.TransitionStyle.DVE:
if (meRef.transitionSettings.DVE) {
switch (meRef.transitionSettings.DVE.style) {
case Enums.DVEEffect.GraphicCCWSpin:
case Enums.DVEEffect.GraphicCWSpin:
case Enums.DVEEffect.GraphicLogoWipe: {
inputs.add(meRef.transitionSettings.DVE.fillSource);
if (meRef.transitionSettings.DVE.enableKey) {
inputs.add(meRef.transitionSettings.DVE.keySource);
}
break;
// Anything not Graphic* do not use the sources
}
}
}
break;
case Enums.TransitionStyle.WIPE:
if (meRef.transitionSettings.wipe && meRef.transitionSettings.wipe.borderWidth > 0) {
inputs.add(meRef.transitionSettings.wipe.borderInput);
}
break;
case Enums.TransitionStyle.STING:
if (meRef.transitionSettings.stinger) {
const mediaPlayerIndex = meRef.transitionSettings.stinger.source;
const fillInputId = 3000 + mediaPlayerIndex * 10;
const keyInputId = fillInputId + 1;
inputs.add(fillInputId);
inputs.add(keyInputId);
}
break;
default:
// Do nothing.
// This is the code path that MIX will take.
// It's already handled above when we add the previewInput
// to the activeInputs array.
}
}
}
/**
* Emulates the ATEM's own tally logic as it appears
* in the ATEM multiviewer.
*
* Useful for any code which needs a full list of all
* sources present in Preview and/or Program.
*/
function listVisibleInputs(mode, state, me = 0) {
const inputs = new Set();
// Start with the basics: the surface level of what is in the target ME.
_calcActiveMeInputs(inputs, mode, state, me);
// Loop over the active input IDs we've found so far,
// and check if any of them are SuperSources or other nested MEs.
// If so, iterate through them and find out what they are showing.
// Keep looping until we stop discovering new things.
let lastSize;
let lastProcessed = 0;
do {
// Only processes inputs we haven't already processed.
// This is an important optimization because this function could potentially
// be in a hot code path and get called many many times a second,
// every time the ATEM's state updates.
lastSize = inputs.size;
for (const inputId of Array.from(inputs).slice(lastProcessed)) {
const input = state.inputs[inputId];
if (!input) {
// Data isn't hydrated yet, we'll get 'em next time.
continue;
}
const portType = input.internalPortType;
switch (portType) {
case Enums.InternalPortType.SuperSource: {
const ssrcId = inputId - 6000;
const ssrc = state_1.AtemStateUtil.getSuperSource(state, ssrcId);
for (const box of ssrc.boxes) {
if (box && box.enabled) {
inputs.add(box.source);
}
}
if (ssrc.properties) {
inputs.add(ssrc.properties.artFillSource);
if (ssrc.properties.artOption === Enums.SuperSourceArtOption.Foreground) {
inputs.add(ssrc.properties.artCutSource);
}
}
break;
}
case Enums.InternalPortType.MEOutput: {
const nestedMeId = (inputId - (inputId % 10) - 10000) / 10 - 1;
const nestedMeMode = (inputId - 10000) % 10 === 0 ? 'program' : 'preview';
_calcActiveMeInputs(inputs, nestedMeMode, state, nestedMeId);
break;
}
default:
// Do nothing.
}
}
lastProcessed = lastSize - 1;
} while (inputs.size !== lastSize);
// undefined sometimes sneaks its way in here.
// Don't know why.
return Array.from(inputs)
.filter((i) => typeof i === 'number')
.sort((a, b) => a - b);
}
exports.listVisibleInputs = listVisibleInputs;
//# sourceMappingURL=tally.js.map