casparcg-state
Version:
Node.js Javascript/Typescript library for keeping and resolving a given state of CasparCG into commands for casparcg-connection.
486 lines • 26.5 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.diffForeground = exports.resolveForegroundState = void 0;
const util_1 = require("../util");
const api_1 = require("../api");
const casparcg_connection_1 = require("casparcg-connection");
const _ = require("underscore");
function diffForeground(oldState, newState, channel, layer, minTimeSincePlay) {
const oldLayer = (0, util_1.getLayer)(oldState, channel, layer);
const newLayer = (0, util_1.getLayer)(newState, channel, layer);
let diff = (0, util_1.compareAttrs)(newLayer, oldLayer, ['content']);
if (!diff) {
if (newLayer.content === api_1.LayerContentType.MEDIA) {
const nl = newLayer;
const ol = oldLayer;
(0, util_1.setDefaultValue)([nl, ol], ['seek', 'length', 'inPoint', 'pauseTime'], 0);
(0, util_1.setDefaultValue)([nl, ol], ['looping', 'playing'], false);
const attrs = [
'media',
// 'playTime',
'looping',
'seek',
'length',
'inPoint',
'pauseTime',
'playing',
'channelLayout',
'vfilter',
'afilter',
];
// Only diff playTime if the new state cares about the value
if (nl.playTime !== null)
attrs.push('playTime');
diff = (0, util_1.compareAttrs)(nl, ol, attrs, minTimeSincePlay);
}
else if (newLayer.content === api_1.LayerContentType.TEMPLATE) {
const nl = newLayer;
const ol = oldLayer;
(0, util_1.setDefaultValue)([nl, ol], ['templateType'], '');
diff = (0, util_1.compareAttrs)(nl, ol, ['media', 'templateType']);
}
else if (newLayer.content === api_1.LayerContentType.HTMLPAGE) {
const nl = newLayer;
const ol = oldLayer;
(0, util_1.setDefaultValue)([nl, ol], ['media'], '');
diff = (0, util_1.compareAttrs)(nl, ol, ['media']);
}
else if (newLayer.content === api_1.LayerContentType.INPUT) {
const nl = newLayer;
const ol = oldLayer;
diff = (0, util_1.compareAttrs)(nl, ol, ['media']);
(0, util_1.setDefaultValue)([nl.input, ol.input], ['device', 'format', 'channelLayout'], '');
if (!diff) {
diff = (0, util_1.compareAttrs)(nl.input, ol.input, ['device', 'format']);
}
}
else if (newLayer.content === api_1.LayerContentType.ROUTE) {
const nl = newLayer;
const ol = oldLayer;
diff = (0, util_1.compareAttrs)(nl, ol, []);
(0, util_1.setDefaultValue)([nl.route, ol.route], ['channel', 'layer'], 0);
diff = (0, util_1.compareAttrs)(nl.route, ol.route, ['channel', 'layer', 'channelLayout']);
if (!diff) {
diff = (0, util_1.compareAttrs)(nl, ol, ['delay', 'mode']);
}
}
else if (newLayer.content === api_1.LayerContentType.RECORD) {
const nl = newLayer;
const ol = oldLayer;
(0, util_1.setDefaultValue)([nl, ol], ['encoderOptions'], '');
diff = (0, util_1.compareAttrs)(nl, ol, ['media', 'playTime', 'encoderOptions'], minTimeSincePlay);
}
else if (newLayer.content === api_1.LayerContentType.FUNCTION) {
const nl = newLayer;
const ol = oldLayer;
diff = (0, util_1.compareAttrs)(nl, ol, ['media']);
}
}
return diff;
}
exports.diffForeground = diffForeground;
function resolveForegroundState(oldState, newState, channel, layer, currentTime, minTimeSincePlay) {
const oldChannel = (0, util_1.getChannel)(oldState, channel);
const newChannel = (0, util_1.getChannel)(newState, channel);
const oldLayer = (0, util_1.getLayer)(oldState, channel, layer);
const newLayer = (0, util_1.getLayer)(newState, channel, layer);
const diffCmds = {
cmds: [],
};
let bgCleared = false;
if (newLayer) {
let diff = diffForeground(oldState, newState, channel, layer, minTimeSincePlay);
if (diff) {
// Added things:
const options = {
channel: newChannel.channelNo,
layer: newLayer.layerNo,
};
(0, util_1.setTransition)(options, newChannel, oldLayer, newLayer.media, false);
if (newLayer.content === api_1.LayerContentType.MEDIA && newLayer.media) {
const nl = newLayer;
const ol = oldLayer;
const timeSincePlay = (0, util_1.getTimeSincePlay)(nl, currentTime, minTimeSincePlay);
let diffMediaFromBg = (0, util_1.compareAttrs)(nl, ol.nextUp, ['media']);
if (options.transition) {
diffMediaFromBg = 'transition';
} // transition changed, so we need to reset
const oldUseLayer = ol.nextUp && !diffMediaFromBg // current media is the one in background
? ol.nextUp
: ol;
const oldTimeSincePlay = ol.nextUp && !diffMediaFromBg ? 0 : (0, util_1.getTimeSincePlay)(ol, currentTime, minTimeSincePlay);
const { inPointFrames, lengthFrames, seekFrames, looping, channelLayout } = (0, util_1.calculatePlayAttributes)(timeSincePlay, nl, newChannel, oldChannel);
const { inPointFrames: oldInPointFrames, lengthFrames: oldLengthFrames, seekFrames: oldSeekFrames, looping: oldLooping, channelLayout: oldChannelLayout, } = (0, util_1.calculatePlayAttributes)(oldTimeSincePlay, oldUseLayer, newChannel, oldChannel);
if (nl.playing) {
nl.pauseTime = 0;
const newMedia = (0, util_1.compareAttrs)(nl, ol, ['media']);
const seekDiff = (0, util_1.frames2TimeChannel)(Math.abs(oldSeekFrames - seekFrames), newChannel, oldChannel);
const seekIsSmall = seekDiff < minTimeSincePlay;
if (!newMedia && ol.pauseTime && seekIsSmall) {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Resume,
params: {
...options,
},
}), `Seek is small (${seekDiff})`, nl));
}
else {
let context = '';
if (newMedia && diffMediaFromBg) {
context = `Media diff from bg: ${newMedia} (${diffMediaFromBg})`;
}
if ((inPointFrames || 0) !== (oldInPointFrames || 0)) {
context = `Inpoints diff (${inPointFrames}, ${oldInPointFrames})`;
} // temporary, until CALL IN command works satisfactory in CasparCG
if ((lengthFrames || 0) !== (oldLengthFrames || 0)) {
context = `Length diff (${lengthFrames}, ${lengthFrames})`;
} // temporary, until CALL LENGTH command works satisfactory in CasparCG
if (!seekIsSmall) {
context = `Seek diff is large (${seekDiff})`;
}
if (looping !== oldLooping) {
context = `Looping diff (${looping}, ${oldLooping})`;
} // temporary, until CALL LOOP works satisfactory in CasparCG
if (channelLayout !== oldChannelLayout) {
context = `ChannelLayout diff (${channelLayout}, ${oldChannelLayout})`;
} // temporary, until CallCommand with channelLayout is implemented in ccg-conn (& casparcg?)
if (oldUseLayer.content !== api_1.LayerContentType.MEDIA || oldUseLayer.afilter !== nl.afilter) {
context = `AFilter diff ("${ol.afilter}", "${nl.afilter}")`;
}
if (oldUseLayer.content !== api_1.LayerContentType.MEDIA || oldUseLayer.vfilter !== nl.vfilter) {
context = `VFilter diff ("${ol.vfilter}", "${nl.vfilter}")`;
}
if (context) {
context += ` (${diff})`;
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Play,
params: (0, util_1.fixPlayCommandInput)({
...options,
clip: (nl.media || '').toString(),
inPoint: inPointFrames,
seek: seekFrames,
length: lengthFrames || undefined,
loop: !!nl.looping,
channelLayout: nl.channelLayout,
clearOn404: nl.clearOn404,
afilter: nl.afilter,
vfilter: nl.vfilter,
}),
}), context, nl));
bgCleared = true;
}
else if (!diffMediaFromBg) {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Play,
params: { ...options },
}), `No Media diff from bg (${nl.media})`, nl));
bgCleared = true;
}
else {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({ command: casparcg_connection_1.Commands.Resume, params: { ...options } }), `Resume otherwise (${diff})`, nl));
if (oldSeekFrames !== seekFrames && !nl.looping) {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Call,
params: { ...options, param: 'SEEK', value: seekFrames },
}), `Seek diff (${seekFrames}, ${oldSeekFrames})`, nl));
}
if (ol.looping !== nl.looping) {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Call,
params: { ...options, param: 'LOOP', value: nl.looping ? 1 : 0 },
}), `Loop diff (${nl.looping}, ${ol.looping})`, nl));
}
if (ol.channelLayout !== nl.channelLayout) {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)({
command: casparcg_connection_1.Commands.Call,
params: {
...options,
param: 'channelLayout',
value: nl.channelLayout + '',
},
}, `ChannelLayout diff (${nl.channelLayout}, ${ol.channelLayout})`, nl));
}
}
}
}
else {
let context = '';
if (_.isNull(timeSincePlay)) {
context = `TimeSincePlay is null (${diff})`;
}
if (nl.pauseTime && timeSincePlay > minTimeSincePlay) {
context = `pauseTime is set (${diff})`;
}
if (context && !(0, util_1.compareAttrs)(nl, ol, ['media'])) {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Pause,
params: {
...options,
// todo: missing param - where is this used?
// pauseTime: nl.pauseTime
},
}), context, nl));
}
else {
if (diffMediaFromBg) {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Load,
params: {
...options,
clip: (nl.media || '').toString(),
inPoint: inPointFrames,
seek: seekFrames,
length: lengthFrames || undefined,
loop: !!nl.looping,
// todo - missing params
// pauseTime: nl.pauseTime,
channelLayout: nl.channelLayout,
clearOn404: nl.clearOn404,
aFilter: nl.afilter,
vFilter: nl.vfilter,
},
}), `Load / Pause otherwise (${diff})`, nl));
}
else {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Load,
params: {
...options,
aFilter: nl.afilter,
vFilter: nl.vfilter,
},
}), `No Media diff from bg (${nl.media})`, nl));
}
bgCleared = true;
}
}
}
else if (newLayer.content === api_1.LayerContentType.TEMPLATE && newLayer.media !== null) {
const nl = newLayer;
// let ol: CasparCG.ITemplateLayer = oldLayer as CasparCG.ITemplateLayer
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.CgAdd,
params: {
...options,
template: (nl.media || '').toString(),
cgLayer: 1,
playOnLoad: nl.playing,
data: nl.templateData || undefined,
// cgStop: nl.cgStop,
// templateType: nl.templateType
},
}), `Add Template (${diff})`, nl));
bgCleared = true;
}
else if (newLayer.content === api_1.LayerContentType.HTMLPAGE && newLayer.media !== null) {
const nl = newLayer;
// let ol: CasparCG.ITemplateLayer = oldLayer as CasparCG.ITemplateLayer
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.PlayHtml,
params: {
...options,
url: (nl.media || '').toString(),
},
}), `Add HTML page (${diff})`, nl));
bgCleared = true;
}
else if (newLayer.content === api_1.LayerContentType.INPUT && newLayer.media !== null) {
const nl = newLayer;
// let ol: CasparCG.IInputLayer = oldLayer as CasparCG.IInputLayer
const inputType = (nl.input && nl.media && (nl.media || '').toString()) || 'decklink';
const device = nl.input && nl.input.device;
const format = (nl.input && nl.input.format) || null;
console.log('format', format);
const channelLayout = (nl.input && nl.input.channelLayout) || null;
if (inputType === 'decklink') {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.PlayDecklink,
params: {
...options,
device: device,
format: format || undefined,
// filter: nl.filter,
channelLayout: channelLayout || undefined,
aFilter: nl.afilter,
vFilter: nl.vfilter,
},
}), `Add decklink (${diff})`, nl));
bgCleared = true;
}
}
else if (newLayer.content === api_1.LayerContentType.ROUTE) {
const nl = newLayer;
const olNext = oldLayer.nextUp;
if (nl.route) {
// const routeChannel: number = nl.route.channel
// const routeLayer: number | null = nl.route.layer || null
const mode = nl.mode;
const framesDelay = nl.delay
? Math.floor((0, util_1.time2FramesChannel)(nl.delay, newChannel, oldChannel))
: undefined;
const diffMediaFromBg = !olNext ||
!olNext.route ||
!(nl.route.channel === olNext.route.channel &&
nl.route.layer === olNext.route.layer &&
nl.delay === olNext.delay);
if (diffMediaFromBg) {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.PlayRoute,
params: {
...options,
route: {
channel: nl.route.channel,
layer: nl.route.layer === null ? undefined : nl.route.layer, // todo - replace with "?? undefined"
},
mode: mode,
framesDelay,
aFilter: nl.afilter,
vFilter: nl.vfilter,
},
}), `Route: diffMediaFromBg (${diff})`, nl));
}
else {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Play,
params: {
...options,
},
}), `Route: no diffMediaFromBg (${diff})`, nl));
}
bgCleared = true;
}
}
else if (newLayer.content === api_1.LayerContentType.RECORD && newLayer.media !== null) {
const nl = newLayer;
// let ol: CasparCG.IRecordLayer = oldLayer as CasparCG.IRecordLayer
const media = nl.media;
const encoderOptions = nl.encoderOptions || '';
// const playTime: any = nl.playTime
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Add,
params: {
channel: options.channel,
consumer: 'FILE',
parameters: media + ' ' + encoderOptions,
// playTime
},
}), `Record (${diff})`, nl));
bgCleared = true; // just to be sure
}
else if (newLayer.content === api_1.LayerContentType.FUNCTION) {
// const nl: FunctionLayer = newLayer as FunctionLayer
// let ol: CasparCG.IFunctionLayer = oldLayer as CasparCG.IFunctionLayer
// if (nl.media && nl.executeFcn) {
// let cmd: AMCPCommandVOWithContext = {
// channel: options.channel,
// layer: options.layer,
// _commandName: 'executeFunction',
// // @ts-ignore special: nl.media used for diffing
// media: nl.media,
// externalFunction: true
// }
// if (nl.executeFcn === 'special_osc') {
// cmd = _.extend(cmd, {
// specialFunction: 'osc',
// oscDevice: nl.oscDevice,
// message: nl.inMessage
// })
// } else {
// cmd = _.extend(cmd, {
// functionName: nl.executeFcn,
// functionData: nl.executeData,
// functionLayer: nl
// })
// }
// cmd = addContext(cmd as any, `Function (${diff})`, nl)
// addCommands(diffCmds, cmd)
// }
}
else {
// oldLayer had content, newLayer had no content, newLayer has a nextup
if (oldLayer.content === api_1.LayerContentType.MEDIA ||
oldLayer.content === api_1.LayerContentType.INPUT ||
oldLayer.content === api_1.LayerContentType.HTMLPAGE ||
oldLayer.content === api_1.LayerContentType.ROUTE
// || oldLayer.content === CasparCG.LayerContentType.MEDIA ???
) {
if (_.isObject(oldLayer.media) && oldLayer.media.outTransition) {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Play,
params: {
channel: oldChannel.channelNo,
layer: oldLayer.layerNo,
clip: 'empty',
...new api_1.Transition(oldLayer.media.outTransition).getOptions(oldChannel.fps),
},
}), `No new content, but old outTransition (${newLayer.content})`, oldLayer));
bgCleared = true;
}
else {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Stop,
params: {
...options,
},
}), `No new content (${newLayer.content})`, oldLayer));
}
}
else if (oldLayer.content === api_1.LayerContentType.TEMPLATE) {
const ol = oldLayer;
if (ol.cgStop) {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.CgStop,
params: {
...options,
cgLayer: 1,
},
}), `No new content, but old cgCgStop (${newLayer.content})`, oldLayer));
}
else {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Clear,
params: { ...options },
}), `No new content (${newLayer.content})`, oldLayer));
bgCleared = true;
}
}
else if (oldLayer.content === api_1.LayerContentType.RECORD) {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.Remove,
params: {
channel: oldChannel.channelNo,
consumer: 'FILE',
},
}), `No new content (${newLayer.content})`, oldLayer));
}
}
}
else if (newLayer.content === api_1.LayerContentType.TEMPLATE) {
const nl = newLayer;
const ol = oldLayer;
diff = (0, util_1.compareAttrs)(nl, ol, ['templateData']);
if (diff) {
// Updated things:
const options = {};
options.channel = newChannel.channelNo;
options.layer = nl.layerNo;
if (nl.content === api_1.LayerContentType.TEMPLATE) {
(0, util_1.addCommands)(diffCmds, (0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.CgUpdate,
params: {
...options,
cgLayer: 1,
data: nl.templateData,
},
}), `Updated templateData`, newLayer));
}
}
}
}
return {
commands: diffCmds,
bgCleared,
};
}
exports.resolveForegroundState = resolveForegroundState;
//# sourceMappingURL=foreground.js.map
;