UNPKG

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