UNPKG

timeline-state-resolver

Version:
189 lines • 8.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.diffStates = void 0; const types_1 = require("./types"); const _ = require("underscore"); const IDEAL_PREPARE_TIME = 1000; const PREPARE_TIME_WAIT = 50; function diffStates(oldState, newState, currentTime) { const time = newState.time; const highPrioCommands = []; const lowPrioCommands = []; const addCommand = (command, lowPriority, context, prelimTime) => { ; (lowPriority ? lowPrioCommands : highPrioCommands).push({ command, timelineObjId: command.timelineObjId, context: context ?? 'Context not specified..', preliminary: prelimTime, }); }; const seenClips = {}; const loadFragments = (portId, port, clip, timelineObjId, isPreloading, context) => { // Only load identical fragments once: const clipIdentifier = `${portId}:${clip.clipId}_${clip.guid}_${clip.title}`; if (!seenClips[clipIdentifier]) { seenClips[clipIdentifier] = true; addCommand({ type: types_1.QuantelCommandType.LOADCLIPFRAGMENTS, portId: portId, timelineObjId: timelineObjId, fromLookahead: isPreloading || port.lookahead, clip: clip, timeOfPlay: time, allowedToPrepareJump: !isPreloading, }, isPreloading || port.lookahead, context, prelimTime); } }; /** The time of when to run "preparation" commands */ const prelimTime = getPreliminaryTime(newState.time, oldState?.time, currentTime); const lookaheadPreloadClips = []; for (const [portId, newPort] of Object.entries(newState.port)) { // diff existing ports const oldPort = oldState?.port[portId]; diffPort(portId, newPort, oldPort, prelimTime, addCommand, loadFragments, lookaheadPreloadClips); } for (const [portId, oldPort] of Object.entries(oldState?.port ?? {})) { // diff old ports that may be removed const newPort = newState.port[portId]; if (!newPort) { // removed port addCommand({ type: types_1.QuantelCommandType.RELEASEPORT, portId: portId, timelineObjId: oldPort.timelineObjId, fromLookahead: oldPort.lookahead, }, oldPort.lookahead, 'Port does not exist in new state', prelimTime); } } // Lookaheads to preload: Object.values(lookaheadPreloadClips).forEach((lookaheadPreloadClip) => { // Preloads of lookaheads are handled last, to ensure that any load-fragments of high-prio clips are done first. loadFragments(lookaheadPreloadClip.portId, lookaheadPreloadClip.port, lookaheadPreloadClip.clip, lookaheadPreloadClip.timelineObjId, true, 'Load from lookahead'); }); const allCommands = highPrioCommands.concat(lowPrioCommands); allCommands.sort((a, b) => { // Release ports should always be done first: if (a.command.type === types_1.QuantelCommandType.RELEASEPORT && b.command.type !== types_1.QuantelCommandType.RELEASEPORT) return -1; if (a.command.type !== types_1.QuantelCommandType.RELEASEPORT && b.command.type === types_1.QuantelCommandType.RELEASEPORT) return 1; return 0; }); // If we run any play-command, we will need to cancel any delayed/waiting out-transitions for that port: const portIdsToCancelWaiting = new Set(); for (const cmd of allCommands) { if ((cmd.command.type === types_1.QuantelCommandType.PLAYCLIP || cmd.command.type === types_1.QuantelCommandType.PAUSECLIP || cmd.command.type === types_1.QuantelCommandType.CLEARCLIP || cmd.command.type === types_1.QuantelCommandType.RELEASEPORT) && !cmd.command.fromLookahead) { // We should clear any delayed out-transitions for this port portIdsToCancelWaiting.add(cmd.command.portId); } } for (const portId of portIdsToCancelWaiting.values()) { // Put first, so that it'll be executed before any other allCommands.unshift({ command: { type: types_1.QuantelCommandType.CANCELWAITING, portId: portId, timelineObjId: '', }, timelineObjId: '', context: 'Clear all delayed out-transitions', // This must be on a unique queueId, so that it "mimics a salvo", ie is executed first thing and not get stuck behind a delayed sequential command. queueId: `reset_port_${portId}`, }); } // console.log('diff', currentTime, allCommands) return allCommands; } exports.diffStates = diffStates; function getPreliminaryTime(time, oldTime, currentTime) { // we want to be at least PREPARE_TIME_WAIT ms after the old state const earliest = Math.max((oldTime ?? 0) + PREPARE_TIME_WAIT, currentTime); // time - earliest = the most delay we can use const maxPrelim = Math.max(0, time - earliest); // the best time is IDEAL_PREPARE_TIME, but we cannot use it if the oldState was too short ago return Math.min(maxPrelim, IDEAL_PREPARE_TIME); } /** diff an existing port */ function diffPort(portId, newPort, oldPort, prelimTime, addCommand, loadFragments, lookaheadPreloadClips) { if (!oldPort || !_.isEqual(newPort.channels, oldPort.channels)) { const channel = newPort.channels[0]; if (channel !== undefined) { // todo: support for multiple channels addCommand({ type: types_1.QuantelCommandType.SETUPPORT, portId: portId, timelineObjId: newPort.timelineObjId, channel: channel, }, newPort.lookahead, 'Old state did not have port', prelimTime); } } if (!oldPort || !_.isEqual(newPort.clip, oldPort.clip)) { if (newPort.clip) { // Load (and play) the clip: let transition; if (oldPort && !oldPort.notOnAir && newPort.notOnAir) { // When the previous content was on-air, we use the out-transition (so that mix-effects look good). // But when the previous content wasn't on-air, we don't wan't to use the out-transition (for example; when cuing previews) transition = oldPort.outTransition; } loadFragments(portId, newPort, newPort.clip, newPort.timelineObjId, false, 'Load from current state'); if (newPort.clip.playing) { addCommand({ type: types_1.QuantelCommandType.PLAYCLIP, portId: portId, timelineObjId: newPort.timelineObjId, fromLookahead: newPort.lookahead, clip: newPort.clip, mode: newPort.mode, transition: transition, }, newPort.lookahead, 'New clip is playing'); } else { addCommand({ type: types_1.QuantelCommandType.PAUSECLIP, portId: portId, timelineObjId: newPort.timelineObjId, fromLookahead: newPort.lookahead, clip: newPort.clip, mode: newPort.mode, transition: transition, }, newPort.lookahead, 'New clip is paused'); } } else { // Stop / Clear the clip addCommand({ type: types_1.QuantelCommandType.CLEARCLIP, portId: portId, timelineObjId: newPort.timelineObjId, fromLookahead: newPort.lookahead, transition: oldPort && oldPort.outTransition, }, newPort.lookahead, 'New clip is empty'); } } if (!oldPort || !_.isEqual(newPort.lookaheadClip, oldPort.lookaheadClip)) { if (newPort.lookaheadClip && (!newPort.clip || newPort.lookaheadClip.clipId !== newPort.clip.clipId || newPort.lookaheadClip.title !== newPort.clip.title || newPort.lookaheadClip.guid !== newPort.clip.guid)) { // Also preload lookaheads later: lookaheadPreloadClips.push({ portId: portId, port: newPort, clip: { ...newPort.lookaheadClip, playTime: 0, playing: false, }, timelineObjId: newPort.lookaheadClip.timelineObjId, }); } } } //# sourceMappingURL=diff.js.map