UNPKG

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
"use strict"; 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