casparcg-state
Version:
Node.js Javascript/Typescript library for keeping and resolving a given state of CasparCG into commands for casparcg-connection.
262 lines • 11.3 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.CasparCGState = exports.CasparCGState0 = void 0;
const _ = require("underscore");
const casparcg_connection_1 = require("casparcg-connection");
// eslint-disable-next-line
const clone = require('fast-clone');
const stateObjectStorage_1 = require("./stateObjectStorage");
const util_1 = require("./util");
const empty_1 = require("./resolvers/empty");
const foreground_1 = require("./resolvers/foreground");
const background_1 = require("./resolvers/background");
const mixer_1 = require("./resolvers/mixer");
const MIN_TIME_SINCE_PLAY = 150; // [ms]
const CasparCGStateVersion = '2017-11-06 19:15';
// config NS
// import {Config as ConfigNS} from "casparcg-connection";
// import CasparCGConfig207 = ConfigNS.v207.CasparCGConfigVO;
// import CasparCGConfig210 = ConfigNS.v21x.CasparCGConfigVO;
/** */
class CasparCGState0 {
// private _externalLog?: (...args: Array<any>) => void
/** */
constructor(config) {
// set the callback for handling media duration query
/* if (config && config.getMediaDurationCallback) {
this._getMediaDuration = (clip: string, channelNo: number, layerNo: number) => {
if (config.getMediaDurationCallback) {
config.getMediaDurationCallback!(clip, (duration: number) => {
this._applyState(channelNo, layerNo, { duration: duration })
})
}
}
} else {
this._getMediaDuration = (clip: string, channelNo: number, layerNo: number) => {
// clip
this._applyState(channelNo, layerNo, { duration: null })
}
} */
this.bufferedCommands = [];
this.minTimeSincePlay = MIN_TIME_SINCE_PLAY; // [ms]
this._currentStateStorage = new stateObjectStorage_1.StateObjectStorage();
// private _getMediaDuration: (clip: string, channelNo: number, layerNo: number) => void
this._isInitialised = false;
// set the callback for handling externalStorage
if (config && config.externalStorage) {
this._currentStateStorage.assignExternalStorage(config.externalStorage);
}
// if (config && config.externalLog) {
// this._externalLog = config.externalLog
// }
}
get version() {
return CasparCGStateVersion;
}
/**
* Initializes the state by using channel info
* @param {any} channels [description]
*/
initStateFromChannelInfo(channels, currentTime) {
const currentState = this._currentStateStorage.fetchState();
_.each(channels, (channel, i) => {
if (!channel.videoMode) {
throw Error('State: Missing channel.videoMode!');
}
if (!channel.fps)
throw Error('State: Missing channel.fps!');
if (!(_.isNumber(channel.fps) && channel.fps > 0)) {
throw Error('State:Bad channel.fps, it should be a number > 0 (got ' + channel.fps + ')!');
}
let existingChannel = currentState.channels[i + 1 + ''];
if (!existingChannel) {
existingChannel = {
channelNo: i + 1,
videoMode: channel.videoMode,
fps: channel.fps,
layers: {},
};
currentState.channels[existingChannel.channelNo] = existingChannel;
}
existingChannel.videoMode = channel.videoMode;
existingChannel.fps = channel.fps;
existingChannel.layers = {};
});
// Save new state:
this._currentStateStorage.storeState(currentState);
this.setIsInitialised(true, currentTime);
}
/**
* Set the current statue to a provided state
* @param {State} state The new state
*/
setState(state) {
this._currentStateStorage.storeState(state);
}
/**
* Get the gurrent state
* @param {true}} options [description]
* @return {InternalState} The current state
*/
getState() {
if (!this.isInitialised) {
throw new Error('CasparCG State is not initialised');
}
return this._currentStateStorage.fetchState();
}
/**
* Resets / clears the current state
*/
clearState() {
this._currentStateStorage.clearState();
this.setIsInitialised(false, 0);
}
/**
* A soft clear, ie clears any content, but keeps channel settings
*/
softClearState() {
const currentState = this._currentStateStorage.fetchState();
_.each(currentState.channels, (channel) => {
channel.layers = {};
});
// Save new state:
this._currentStateStorage.storeState(currentState);
}
getDiff(newState, currentTime) {
// needs to be initialised
if (!this.isInitialised) {
throw new Error('CasparCG State is not initialised');
}
const currentState = this._currentStateStorage.fetchState();
return CasparCGState0.diffStates(currentState, newState, currentTime, this.minTimeSincePlay);
}
/**
* Temporary, intermediate function, to deal with ordering of commands. (This might be replaced with something more permanent later)
* @param oldState
* @param newState
*/
static diffStatesOrderedCommands(oldState, newState, currentTime, minTimeSincePlay = MIN_TIME_SINCE_PLAY) {
const diff = CasparCGState0.diffStates(oldState, newState, currentTime, minTimeSincePlay);
const fastCommands = []; // fast to exec, and direct visual impact: PLAY 1-10
const slowCommands = []; // slow to exec, but direct visual impact: PLAY 1-10 FILE (needs to have all commands for that layer in the right order)
const lowPrioCommands = []; // slow to exec, and no direct visual impact: LOADBG 1-10 FILE
for (const layer of diff) {
let containsSlowCommand = false;
// filter out lowPrioCommands
for (let i = 0; i < layer.cmds.length; i++) {
if (layer.cmds[i].command === casparcg_connection_1.Commands.Loadbg ||
layer.cmds[i].command === casparcg_connection_1.Commands.LoadbgDecklink ||
layer.cmds[i].command === casparcg_connection_1.Commands.LoadbgRoute ||
layer.cmds[i].command === casparcg_connection_1.Commands.LoadbgHtml) {
lowPrioCommands.push(layer.cmds[i]);
layer.cmds.splice(i, 1);
i--; // next entry now has the same index as this one.
}
else if ((layer.cmds[i].command === casparcg_connection_1.Commands.Play && layer.cmds[i].params.clip) ||
(layer.cmds[i].command === casparcg_connection_1.Commands.PlayDecklink &&
layer.cmds[i].params.device) ||
(layer.cmds[i].command === casparcg_connection_1.Commands.PlayRoute &&
layer.cmds[i].params.route) ||
(layer.cmds[i].command === casparcg_connection_1.Commands.PlayHtml && layer.cmds[i].params.url) ||
layer.cmds[i].command === casparcg_connection_1.Commands.Load // ||
// layer.cmds[i].command === 'LoadDecklinkCommand' ||
// layer.cmds[i].command === 'LoadRouteCommand' ||
// layer.cmds[i].command === 'LoadHtmlPageCommand'
) {
containsSlowCommand = true;
}
}
if (containsSlowCommand) {
slowCommands.push(...layer.cmds);
}
else {
fastCommands.push(...layer.cmds);
}
}
return [...fastCommands, ...slowCommands, ...lowPrioCommands];
}
/** */
static diffStates(oldState, newState, currentTime, minTimeSincePlay = MIN_TIME_SINCE_PLAY) {
const commands = [];
const bundledCmds = {};
// Added/updated things:
for (const [channelKey, newChannel] of Object.entries(newState.channels)) {
for (const [layerKey, newLayer] of Object.entries(newChannel.layers)) {
const fgChanges = (0, foreground_1.resolveForegroundState)(oldState, newState, channelKey, layerKey, currentTime, minTimeSincePlay);
const bgChanges = (0, background_1.resolveBackgroundState)(oldState, newState, channelKey, layerKey, fgChanges.bgCleared);
const mixerChanges = (0, mixer_1.resolveMixerState)(oldState, newState, channelKey, layerKey);
const diffCmds = {
cmds: [...fgChanges.commands.cmds, ...bgChanges.commands.cmds, ...mixerChanges.commands.cmds],
additionalLayerState: newLayer,
};
commands.push(diffCmds);
for (const group of Object.keys(mixerChanges.bundledCmds)) {
bundledCmds[group] = [...(bundledCmds[group] || []), ...mixerChanges.bundledCmds[group]];
}
}
}
// Removed things:
for (const [channelKey, oldChannel] of Object.entries(oldState.channels)) {
for (const layerKey of Object.keys(oldChannel.layers)) {
const diff = (0, empty_1.resolveEmptyState)(oldState, newState, channelKey, layerKey);
if (diff.commands.cmds.length)
commands.push(diff.commands);
}
}
// bundled commands:
_.each(bundledCmds, (bundle) => {
const channels = _.uniq(bundle.map((c) => c.params.channel));
// const channels = _.uniq(_.pluck(bundle, 'channel'))
_.each(channels, (channel) => {
bundle.push((0, util_1.addContext)((0, util_1.literal)({
command: casparcg_connection_1.Commands.MixerCommit,
params: {
channel: Number(channel),
},
}), `Bundle commit`, null));
});
const diffCmds = {
cmds: [],
};
(0, util_1.addCommands)(diffCmds, ...bundle);
commands.push(diffCmds);
});
return commands;
}
valueOf() {
return this.getState();
}
toString() {
return JSON.stringify(this.getState());
}
/** */
get isInitialised() {
return this._isInitialised;
}
/** */
setIsInitialised(initialised, _currentTime) {
if (this._isInitialised !== initialised) {
this._isInitialised = initialised;
}
}
}
exports.CasparCGState0 = CasparCGState0;
class CasparCGState extends CasparCGState0 {
/**
* Set the current state to provided state
* @param state The new state
*/
setState(state) {
super.setState(clone(state));
}
/**
* Get the gurrent state
* @param {true}} options [description]
* @return {InternalState} The current state
*/
getState() {
return clone(super.getState());
}
}
exports.CasparCGState = CasparCGState;
//# sourceMappingURL=casparCGState.js.map
;