From 062e9775ed67982bbd02b530aea180fe6c20ffd9 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Fri, 6 Dec 2019 20:14:08 +0000 Subject: [PATCH] feat: attempt to handle out of range features safely --- src/__tests__/tally.spec.ts | 11 ++++++-- src/__tests__/util.ts | 25 +++++++++++++++++++ src/commands/DeviceProfile/topologyCommand.ts | 9 +++++-- .../DownstreamKeyPropertiesCommand.ts | 4 +++ .../DownstreamKeySourcesCommand.ts | 4 +++ .../DownstreamKeyStateCommand.ts | 8 ++++++ .../Media/MediaPlayerSourceCommand.ts | 4 +++ .../Media/MediaPlayerStatusCommand.ts | 4 +++ .../Media/MediaPoolClipDescription.ts | 12 +++++---- .../Media/MediaPoolFrameDescription.ts | 11 +++++--- .../FadeToBlack/FadeToBlackRateCommand.ts | 4 +++ .../FadeToBlack/FadeToBlackStateCommand.ts | 4 +++ .../Key/MixEffectKeyChromaCommand.ts | 4 +++ .../MixEffects/Key/MixEffectKeyDVECommand.ts | 4 +++ .../Key/MixEffectKeyFlyKeyframeGetCommand.ts | 4 +++ .../MixEffectKeyFlyPropertiesGetCommand.ts | 4 +++ .../MixEffects/Key/MixEffectKeyLumaCommand.ts | 4 +++ .../Key/MixEffectKeyOnAirCommand.ts | 4 +++ .../Key/MixEffectKeyPatternCommand.ts | 4 +++ .../Key/MixEffectKeyPropertiesGetCommand.ts | 13 +++++++--- .../MixEffects/PreviewInputCommand.ts | 4 +++ .../MixEffects/ProgramInputCommand.ts | 4 +++ .../Transition/TransitionDVECommand.ts | 4 +++ .../Transition/TransitionDipCommand.ts | 4 +++ .../Transition/TransitionMixCommand.ts | 4 +++ .../Transition/TransitionPositionCommand.ts | 4 +++ .../Transition/TransitionPreviewCommand.ts | 4 +++ .../Transition/TransitionPropertiesCommand.ts | 4 +++ .../Transition/TransitionStingerCommand.ts | 4 +++ .../Transition/TransitionWipeCommand.ts | 4 +++ .../Settings/MultiViewerSourceCommand.ts | 4 +++ .../SuperSourceBoxParametersCommand.ts | 4 +++ .../SuperSourcePropertiesCommand.ts | 12 +++++++++ src/commands/__tests__/index.spec.ts | 8 +++++- src/commands/__tests__/util.ts | 4 +-- src/commands/__tests__/v8.0.spec.ts | 8 +++++- src/lib/__tests__/atemSocket.spec.ts | 4 +-- src/lib/atemSocket.ts | 2 +- src/state/info.ts | 5 +++- 39 files changed, 208 insertions(+), 24 deletions(-) diff --git a/src/__tests__/tally.spec.ts b/src/__tests__/tally.spec.ts index 5ff070097..64b5beb86 100644 --- a/src/__tests__/tally.spec.ts +++ b/src/__tests__/tally.spec.ts @@ -3,7 +3,7 @@ import { listVisibleInputs } from '../lib/tally' import { readFileSync } from 'fs' import { resolve } from 'path' -import { parseAtemState } from './util' +import { parseAtemState, createEmptyState } from './util' function readJson (fileName: string) { const filePath = resolve(__dirname, fileName) @@ -12,7 +12,14 @@ function readJson (fileName: string) { } function loadRawState (file: string) { - return parseAtemState(readJson(`./tally/${file}-state.json`)) + const loadedState = parseAtemState(readJson(`./tally/${file}-state.json`)) + + if (!loadedState.info.capabilities) { + const emptyState = createEmptyState() + loadedState.info.capabilities = emptyState.info.capabilities + } + + return loadedState } function loadTally (file: string) { const rawTally = readJson(`./tally/${file}-tally.json`) as Commands.TallyBySourceCommand['properties'] diff --git a/src/__tests__/util.ts b/src/__tests__/util.ts index 3addca7fe..762b256a5 100644 --- a/src/__tests__/util.ts +++ b/src/__tests__/util.ts @@ -27,3 +27,28 @@ export function parseAtemState (rawState: any): AtemState { return state } + +export function createEmptyState () { + const state = new AtemState() + + // These should be the maximum supported by any device. + // But they can also be whatever is needed to allow the tests to run without error + state.info.capabilities = { + mixEffects: 4, + sources: 40, + colorGenerators: 2, + auxilliaries: 6, + talkbackOutputs: 8, + mediaPlayers: 4, + serialPorts: 1, + maxHyperdecks: 4, + DVEs: 1, + stingers: 1, + superSources: 2, + talkbackOverSDI: 0, + multiViewers: 255, + downstreamKeyers: 4 + } + + return state +} \ No newline at end of file diff --git a/src/commands/DeviceProfile/topologyCommand.ts b/src/commands/DeviceProfile/topologyCommand.ts index 7c39c2675..57d35cdbc 100644 --- a/src/commands/DeviceProfile/topologyCommand.ts +++ b/src/commands/DeviceProfile/topologyCommand.ts @@ -7,7 +7,7 @@ export class TopologyCommand extends DeserializedCommand { public static deserialize (rawCommand: Buffer) { const properties = { - MEs: rawCommand[0], + mixEffects: rawCommand[0], sources: rawCommand[1], colorGenerators: rawCommand[2], auxilliaries: rawCommand[3], @@ -18,7 +18,12 @@ export class TopologyCommand extends DeserializedCommand { DVEs: rawCommand[8], stingers: rawCommand[9], superSources: rawCommand[10], - talkbackOverSDI: rawCommand[13] + talkbackOverSDI: rawCommand[13], + + // TODO - define the below properly + multiViewers: 2, + downstreamKeyers: 2, + upstreamKeyers: 2 } return new TopologyCommand(properties) diff --git a/src/commands/DownstreamKey/DownstreamKeyPropertiesCommand.ts b/src/commands/DownstreamKey/DownstreamKeyPropertiesCommand.ts index a17e2a4ba..d43969cd8 100644 --- a/src/commands/DownstreamKey/DownstreamKeyPropertiesCommand.ts +++ b/src/commands/DownstreamKey/DownstreamKeyPropertiesCommand.ts @@ -37,6 +37,10 @@ export class DownstreamKeyPropertiesCommand extends DeserializedCommand= state.info.capabilities.downstreamKeyers) { + throw new Error(`DownstreamKeyer ${this.downstreamKeyerId} is not valid`) + } + state.video.getDownstreamKeyer(this.downstreamKeyerId).properties = this.properties return `video.downstreamKeyers.${this.downstreamKeyerId}` } diff --git a/src/commands/DownstreamKey/DownstreamKeySourcesCommand.ts b/src/commands/DownstreamKey/DownstreamKeySourcesCommand.ts index e13b46eee..03f417dfe 100644 --- a/src/commands/DownstreamKey/DownstreamKeySourcesCommand.ts +++ b/src/commands/DownstreamKey/DownstreamKeySourcesCommand.ts @@ -24,6 +24,10 @@ export class DownstreamKeySourcesCommand extends DeserializedCommand= state.info.capabilities.downstreamKeyers) { + throw new Error(`DownstreamKeyer ${this.downstreamKeyerId} is not valid`) + } + state.video.getDownstreamKeyer(this.downstreamKeyerId).sources = this.properties return `video.downstreamKeyers.${this.downstreamKeyerId}` } diff --git a/src/commands/DownstreamKey/DownstreamKeyStateCommand.ts b/src/commands/DownstreamKey/DownstreamKeyStateCommand.ts index dd69bce13..580365b0c 100644 --- a/src/commands/DownstreamKey/DownstreamKeyStateCommand.ts +++ b/src/commands/DownstreamKey/DownstreamKeyStateCommand.ts @@ -27,6 +27,10 @@ export class DownstreamKeyStateCommand extends DeserializedCommand= state.info.capabilities.downstreamKeyers) { + throw new Error(`DownstreamKeyer ${this.downstreamKeyerId} is not valid`) + } + state.video.downstreamKeyers[this.downstreamKeyerId] = { ...state.video.getDownstreamKeyer(this.downstreamKeyerId), ...this.properties @@ -61,6 +65,10 @@ export class DownstreamKeyStateV8Command extends DeserializedCommand= state.info.capabilities.downstreamKeyers) { + throw new Error(`DownstreamKeyer ${this.downstreamKeyerId} is not valid`) + } + state.video.downstreamKeyers[this.downstreamKeyerId] = { ...state.video.getDownstreamKeyer(this.downstreamKeyerId), ...this.properties diff --git a/src/commands/Media/MediaPlayerSourceCommand.ts b/src/commands/Media/MediaPlayerSourceCommand.ts index 7c53ae1c2..5e084d963 100644 --- a/src/commands/Media/MediaPlayerSourceCommand.ts +++ b/src/commands/Media/MediaPlayerSourceCommand.ts @@ -53,6 +53,10 @@ export class MediaPlayerSourceUpdateCommand extends DeserializedCommand= state.info.capabilities.mediaPlayers) { + throw new Error(`MediaPlayer ${this.mediaPlayerId} is not valid`) + } + state.media.players[this.mediaPlayerId] = { ...state.media.getMediaPlayer(this.mediaPlayerId), ...this.properties diff --git a/src/commands/Media/MediaPlayerStatusCommand.ts b/src/commands/Media/MediaPlayerStatusCommand.ts index b4e073537..423f72750 100644 --- a/src/commands/Media/MediaPlayerStatusCommand.ts +++ b/src/commands/Media/MediaPlayerStatusCommand.ts @@ -55,6 +55,10 @@ export class MediaPlayerStatusUpdateCommand extends DeserializedCommand= state.info.capabilities.mediaPlayers) { + throw new Error(`MediaPlayer ${this.mediaPlayerId} is not valid`) + } + state.media.players[this.mediaPlayerId] = { ...state.media.getMediaPlayer(this.mediaPlayerId), ...this.properties diff --git a/src/commands/Media/MediaPoolClipDescription.ts b/src/commands/Media/MediaPoolClipDescription.ts index 69a48a57d..baad63d9b 100644 --- a/src/commands/Media/MediaPoolClipDescription.ts +++ b/src/commands/Media/MediaPoolClipDescription.ts @@ -6,12 +6,12 @@ import { Util } from '../../lib/atemUtil' export class MediaPoolClipDescriptionCommand extends DeserializedCommand> { public static readonly rawName = 'MPCS' - public readonly mediaPool: number + public readonly clipId: number constructor (mediaPool: number, properties: Omit) { super(properties) - this.mediaPool = mediaPool + this.clipId = mediaPool } public static deserialize (rawCommand: Buffer) { @@ -26,10 +26,12 @@ export class MediaPoolClipDescriptionCommand extends DeserializedCommand= state.info.capabilities.mixEffects) { + throw new Error(`MixEffect ${this.mixEffect} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) mixEffect.fadeToBlack = { isFullyBlack: false, diff --git a/src/commands/MixEffects/FadeToBlack/FadeToBlackStateCommand.ts b/src/commands/MixEffects/FadeToBlack/FadeToBlackStateCommand.ts index 2cf30d2fe..fd31bf184 100644 --- a/src/commands/MixEffects/FadeToBlack/FadeToBlackStateCommand.ts +++ b/src/commands/MixEffects/FadeToBlack/FadeToBlackStateCommand.ts @@ -30,6 +30,10 @@ export class FadeToBlackStateCommand extends DeserializedCommand= state.info.capabilities.mixEffects) { + throw new Error(`MixEffect ${this.mixEffect} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) mixEffect.fadeToBlack = { rate: 0, diff --git a/src/commands/MixEffects/Key/MixEffectKeyChromaCommand.ts b/src/commands/MixEffects/Key/MixEffectKeyChromaCommand.ts index 6469cf9ea..138972b15 100644 --- a/src/commands/MixEffects/Key/MixEffectKeyChromaCommand.ts +++ b/src/commands/MixEffects/Key/MixEffectKeyChromaCommand.ts @@ -66,6 +66,10 @@ export class MixEffectKeyChromaUpdateCommand extends DeserializedCommand= state.info.capabilities.mixEffects || this.upstreamKeyerId >= state.info.capabilities.upstreamKeyers) { + throw new Error(`UpstreamKeyer ${this.mixEffect}-${this.upstreamKeyerId} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) const upstreamKeyer = mixEffect.getUpstreamKeyer(this.upstreamKeyerId) upstreamKeyer.chromaSettings = { diff --git a/src/commands/MixEffects/Key/MixEffectKeyDVECommand.ts b/src/commands/MixEffects/Key/MixEffectKeyDVECommand.ts index be61c7e5a..13559d24c 100644 --- a/src/commands/MixEffects/Key/MixEffectKeyDVECommand.ts +++ b/src/commands/MixEffects/Key/MixEffectKeyDVECommand.ts @@ -141,6 +141,10 @@ export class MixEffectKeyDVEUpdateCommand extends DeserializedCommand= state.info.capabilities.mixEffects || this.upstreamKeyerId >= state.info.capabilities.upstreamKeyers) { + throw new Error(`UpstreamKeyer ${this.mixEffect}-${this.upstreamKeyerId} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) const upstreamKeyer = mixEffect.getUpstreamKeyer(this.upstreamKeyerId) upstreamKeyer.dveSettings = { diff --git a/src/commands/MixEffects/Key/MixEffectKeyFlyKeyframeGetCommand.ts b/src/commands/MixEffects/Key/MixEffectKeyFlyKeyframeGetCommand.ts index 2abab0a10..fd06232c3 100644 --- a/src/commands/MixEffects/Key/MixEffectKeyFlyKeyframeGetCommand.ts +++ b/src/commands/MixEffects/Key/MixEffectKeyFlyKeyframeGetCommand.ts @@ -57,6 +57,10 @@ export class MixEffectKeyFlyKeyframeGetCommand extends DeserializedCommand= state.info.capabilities.mixEffects || this.upstreamKeyerId >= state.info.capabilities.upstreamKeyers) { + throw new Error(`UpstreamKeyer ${this.mixEffect}-${this.upstreamKeyerId} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) const upstreamKeyer = mixEffect.getUpstreamKeyer(this.upstreamKeyerId) upstreamKeyer.flyKeyframes[this.properties.keyFrameId] = { diff --git a/src/commands/MixEffects/Key/MixEffectKeyFlyPropertiesGetCommand.ts b/src/commands/MixEffects/Key/MixEffectKeyFlyPropertiesGetCommand.ts index 9ea9607fb..3f7a60df4 100644 --- a/src/commands/MixEffects/Key/MixEffectKeyFlyPropertiesGetCommand.ts +++ b/src/commands/MixEffects/Key/MixEffectKeyFlyPropertiesGetCommand.ts @@ -28,6 +28,10 @@ export class MixEffectKeyFlyPropertiesGetCommand extends DeserializedCommand= state.info.capabilities.mixEffects || this.upstreamKeyerId >= state.info.capabilities.upstreamKeyers) { + throw new Error(`UpstreamKeyer ${this.mixEffect}-${this.upstreamKeyerId} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) const upstreamKeyer = mixEffect.getUpstreamKeyer(this.upstreamKeyerId) upstreamKeyer.flyProperties = { diff --git a/src/commands/MixEffects/Key/MixEffectKeyLumaCommand.ts b/src/commands/MixEffects/Key/MixEffectKeyLumaCommand.ts index 09b374a76..f91478126 100644 --- a/src/commands/MixEffects/Key/MixEffectKeyLumaCommand.ts +++ b/src/commands/MixEffects/Key/MixEffectKeyLumaCommand.ts @@ -63,6 +63,10 @@ export class MixEffectKeyLumaUpdateCommand extends DeserializedCommand= state.info.capabilities.mixEffects || this.upstreamKeyerId >= state.info.capabilities.upstreamKeyers) { + throw new Error(`UpstreamKeyer ${this.mixEffect}-${this.upstreamKeyerId} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) const upstreamKeyer = mixEffect.getUpstreamKeyer(this.upstreamKeyerId) upstreamKeyer.lumaSettings = { diff --git a/src/commands/MixEffects/Key/MixEffectKeyOnAirCommand.ts b/src/commands/MixEffects/Key/MixEffectKeyOnAirCommand.ts index daabfc043..519a4ab0c 100644 --- a/src/commands/MixEffects/Key/MixEffectKeyOnAirCommand.ts +++ b/src/commands/MixEffects/Key/MixEffectKeyOnAirCommand.ts @@ -46,6 +46,10 @@ export class MixEffectKeyOnAirUpdateCommand extends DeserializedCommand<{onAir: } public applyToState (state: AtemState) { + if (!state.info.capabilities || this.mixEffect >= state.info.capabilities.mixEffects || this.upstreamKeyerId >= state.info.capabilities.upstreamKeyers) { + throw new Error(`UpstreamKeyer ${this.mixEffect}-${this.upstreamKeyerId} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) const upstreamKeyer = mixEffect.getUpstreamKeyer(this.upstreamKeyerId) upstreamKeyer.onAir = this.properties.onAir diff --git a/src/commands/MixEffects/Key/MixEffectKeyPatternCommand.ts b/src/commands/MixEffects/Key/MixEffectKeyPatternCommand.ts index 5d13cae1d..67cadfc5e 100644 --- a/src/commands/MixEffects/Key/MixEffectKeyPatternCommand.ts +++ b/src/commands/MixEffects/Key/MixEffectKeyPatternCommand.ts @@ -73,6 +73,10 @@ export class MixEffectKeyUpdateCommand extends DeserializedCommand= state.info.capabilities.mixEffects || this.upstreamKeyerId >= state.info.capabilities.upstreamKeyers) { + throw new Error(`UpstreamKeyer ${this.mixEffect}-${this.upstreamKeyerId} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) const upstreamKeyer = mixEffect.getUpstreamKeyer(this.upstreamKeyerId) upstreamKeyer.patternSettings = { diff --git a/src/commands/MixEffects/Key/MixEffectKeyPropertiesGetCommand.ts b/src/commands/MixEffects/Key/MixEffectKeyPropertiesGetCommand.ts index 05821bd0a..ec520d30b 100644 --- a/src/commands/MixEffects/Key/MixEffectKeyPropertiesGetCommand.ts +++ b/src/commands/MixEffects/Key/MixEffectKeyPropertiesGetCommand.ts @@ -6,17 +6,20 @@ export class MixEffectKeyPropertiesGetCommand extends DeserializedCommand= state.info.capabilities.mixEffects || this.upstreamKeyerId >= state.info.capabilities.upstreamKeyers) { + throw new Error(`UpstreamKeyer ${this.mixEffect}-${this.upstreamKeyerId} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) mixEffect.upstreamKeyers[this.properties.upstreamKeyerId] = { ...mixEffect.getUpstreamKeyer(this.properties.upstreamKeyerId), diff --git a/src/commands/MixEffects/PreviewInputCommand.ts b/src/commands/MixEffects/PreviewInputCommand.ts index a12b2936a..48a239ff4 100644 --- a/src/commands/MixEffects/PreviewInputCommand.ts +++ b/src/commands/MixEffects/PreviewInputCommand.ts @@ -45,6 +45,10 @@ export class PreviewInputUpdateCommand extends DeserializedCommand } public applyToState (state: AtemState) { + if (!state.info.capabilities || this.mixEffect >= state.info.capabilities.mixEffects) { + throw new Error(`MixEffect ${this.mixEffect} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) mixEffect.previewInput = this.properties.source return `video.ME.${this.mixEffect}.previewInput` diff --git a/src/commands/MixEffects/ProgramInputCommand.ts b/src/commands/MixEffects/ProgramInputCommand.ts index bc905e128..5f66203a2 100644 --- a/src/commands/MixEffects/ProgramInputCommand.ts +++ b/src/commands/MixEffects/ProgramInputCommand.ts @@ -42,6 +42,10 @@ export class ProgramInputUpdateCommand extends DeserializedCommand } public applyToState (state: AtemState) { + if (!state.info.capabilities || this.mixEffect >= state.info.capabilities.mixEffects) { + throw new Error(`MixEffect ${this.mixEffect} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) mixEffect.programInput = this.properties.source return `video.ME.${this.mixEffect}.programInput` diff --git a/src/commands/MixEffects/Transition/TransitionDVECommand.ts b/src/commands/MixEffects/Transition/TransitionDVECommand.ts index 1fc8babed..e24385d87 100644 --- a/src/commands/MixEffects/Transition/TransitionDVECommand.ts +++ b/src/commands/MixEffects/Transition/TransitionDVECommand.ts @@ -85,6 +85,10 @@ export class TransitionDVEUpdateCommand extends DeserializedCommand= state.info.capabilities.mixEffects) { + throw new Error(`MixEffect ${this.mixEffect} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) mixEffect.transitionSettings.DVE = { ...this.properties diff --git a/src/commands/MixEffects/Transition/TransitionDipCommand.ts b/src/commands/MixEffects/Transition/TransitionDipCommand.ts index 7173fa220..fb8db3b3a 100644 --- a/src/commands/MixEffects/Transition/TransitionDipCommand.ts +++ b/src/commands/MixEffects/Transition/TransitionDipCommand.ts @@ -49,6 +49,10 @@ export class TransitionDipUpdateCommand extends DeserializedCommand= state.info.capabilities.mixEffects) { + throw new Error(`MixEffect ${this.mixEffect} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) mixEffect.transitionSettings.dip = { ...this.properties diff --git a/src/commands/MixEffects/Transition/TransitionMixCommand.ts b/src/commands/MixEffects/Transition/TransitionMixCommand.ts index 907869c6f..856db8d19 100644 --- a/src/commands/MixEffects/Transition/TransitionMixCommand.ts +++ b/src/commands/MixEffects/Transition/TransitionMixCommand.ts @@ -42,6 +42,10 @@ export class TransitionMixUpdateCommand extends DeserializedCommand= state.info.capabilities.mixEffects) { + throw new Error(`MixEffect ${this.mixEffect} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) mixEffect.transitionSettings.mix = { ...this.properties diff --git a/src/commands/MixEffects/Transition/TransitionPositionCommand.ts b/src/commands/MixEffects/Transition/TransitionPositionCommand.ts index 2b034d81b..ed0685a9a 100644 --- a/src/commands/MixEffects/Transition/TransitionPositionCommand.ts +++ b/src/commands/MixEffects/Transition/TransitionPositionCommand.ts @@ -52,6 +52,10 @@ export class TransitionPositionUpdateCommand extends DeserializedCommand= state.info.capabilities.mixEffects) { + throw new Error(`MixEffect ${this.mixEffect} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) mixEffect.transitionFramesLeft = this.properties.remainingFrames mixEffect.transitionPosition = this.properties.handlePosition diff --git a/src/commands/MixEffects/Transition/TransitionPreviewCommand.ts b/src/commands/MixEffects/Transition/TransitionPreviewCommand.ts index c6d9764b5..a97dc309b 100644 --- a/src/commands/MixEffects/Transition/TransitionPreviewCommand.ts +++ b/src/commands/MixEffects/Transition/TransitionPreviewCommand.ts @@ -45,6 +45,10 @@ export class PreviewTransitionUpdateCommand extends DeserializedCommand= state.info.capabilities.mixEffects) { + throw new Error(`MixEffect ${this.mixEffect} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) mixEffect.transitionPreview = this.properties.preview return `video.ME.${this.mixEffect}.transitionPreview` diff --git a/src/commands/MixEffects/Transition/TransitionPropertiesCommand.ts b/src/commands/MixEffects/Transition/TransitionPropertiesCommand.ts index 514f045b3..170451044 100644 --- a/src/commands/MixEffects/Transition/TransitionPropertiesCommand.ts +++ b/src/commands/MixEffects/Transition/TransitionPropertiesCommand.ts @@ -54,6 +54,10 @@ export class TransitionPropertiesUpdateCommand extends DeserializedCommand= state.info.capabilities.mixEffects) { + throw new Error(`MixEffect ${this.mixEffect} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) mixEffect.transitionProperties = { ...this.properties diff --git a/src/commands/MixEffects/Transition/TransitionStingerCommand.ts b/src/commands/MixEffects/Transition/TransitionStingerCommand.ts index 2dd81c6e1..d5d39538c 100644 --- a/src/commands/MixEffects/Transition/TransitionStingerCommand.ts +++ b/src/commands/MixEffects/Transition/TransitionStingerCommand.ts @@ -77,6 +77,10 @@ export class TransitionStingerUpdateCommand extends DeserializedCommand= state.info.capabilities.mixEffects) { + throw new Error(`MixEffect ${this.mixEffect} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) mixEffect.transitionSettings.stinger = { ...this.properties diff --git a/src/commands/MixEffects/Transition/TransitionWipeCommand.ts b/src/commands/MixEffects/Transition/TransitionWipeCommand.ts index 0fdb38e6c..7f6f6b285 100644 --- a/src/commands/MixEffects/Transition/TransitionWipeCommand.ts +++ b/src/commands/MixEffects/Transition/TransitionWipeCommand.ts @@ -78,6 +78,10 @@ export class TransitionWipeUpdateCommand extends DeserializedCommand= state.info.capabilities.mixEffects) { + throw new Error(`MixEffect ${this.mixEffect} is not valid`) + } + const mixEffect = state.video.getMe(this.mixEffect) mixEffect.transitionSettings.wipe = { ...this.properties diff --git a/src/commands/Settings/MultiViewerSourceCommand.ts b/src/commands/Settings/MultiViewerSourceCommand.ts index 54c37df67..34fe64592 100644 --- a/src/commands/Settings/MultiViewerSourceCommand.ts +++ b/src/commands/Settings/MultiViewerSourceCommand.ts @@ -44,6 +44,10 @@ export class MultiViewerSourceUpdateCommand extends DeserializedCommand= state.info.capabilities.multiViewers) { + throw new Error(`MultiViewer ${this.multiViewerId} is not valid`) + } + const multiviewer = state.settings.getMultiViewer(this.multiViewerId) multiviewer.windows[this.properties.windowIndex] = { ...multiviewer.windows[this.properties.windowIndex], diff --git a/src/commands/SuperSource/SuperSourceBoxParametersCommand.ts b/src/commands/SuperSource/SuperSourceBoxParametersCommand.ts index 50545d6e3..58e85a561 100644 --- a/src/commands/SuperSource/SuperSourceBoxParametersCommand.ts +++ b/src/commands/SuperSource/SuperSourceBoxParametersCommand.ts @@ -94,6 +94,10 @@ export class SuperSourceBoxParametersUpdateCommand extends DeserializedCommand= state.info.capabilities.superSources) { + throw new Error(`SuperSource ${this.ssrcId} is not valid`) + } + const supersource = state.video.getSuperSource(this.ssrcId) supersource.boxes[this.boxId] = { ...this.properties diff --git a/src/commands/SuperSource/SuperSourcePropertiesCommand.ts b/src/commands/SuperSource/SuperSourcePropertiesCommand.ts index a71b5037b..c88a4b6d1 100644 --- a/src/commands/SuperSource/SuperSourcePropertiesCommand.ts +++ b/src/commands/SuperSource/SuperSourcePropertiesCommand.ts @@ -191,6 +191,10 @@ export class SuperSourcePropertiesUpdateCommand extends DeserializedCommand<{ pr } public applyToState (state: AtemState) { + if (!state.info.capabilities || !state.info.capabilities.superSources) { + throw new Error(`SuperSource 0 is not valid`) + } + const supersource = state.video.getSuperSource(0) supersource.properties = this.properties.properties supersource.border = this.properties.border @@ -229,6 +233,10 @@ export class SuperSourcePropertiesUpdateV8Command extends DeserializedCommand= state.info.capabilities.superSources) { + throw new Error(`SuperSource ${this.ssrcId} is not valid`) + } + const supersource = state.video.getSuperSource(this.ssrcId) supersource.properties = { ...this.properties @@ -271,6 +279,10 @@ export class SuperSourceBorderUpdateCommand extends DeserializedCommand= state.info.capabilities.superSources) { + throw new Error(`SuperSource ${this.ssrcId} is not valid`) + } + const supersource = state.video.getSuperSource(this.ssrcId) supersource.border = this.properties diff --git a/src/commands/__tests__/index.spec.ts b/src/commands/__tests__/index.spec.ts index 0339c7587..0f1ca2d90 100644 --- a/src/commands/__tests__/index.spec.ts +++ b/src/commands/__tests__/index.spec.ts @@ -257,11 +257,17 @@ const commandConverters: CommandTestConverterSet = { 'auxiliaries': (val: any) => ({ val, name: 'auxilliaries' }), 'dVE': (val: any) => ({ val, name: 'DVEs' }), 'hyperDecks': (val: any) => ({ val, name: 'maxHyperdecks' }), - 'mixEffectBlocks': (val: any) => ({ val, name: 'MEs' }), + 'mixEffectBlocks': (val: any) => ({ val, name: 'mixEffects' }), 'serialPort': (val: any) => ({ val, name: 'serialPorts' }), 'videoSources': (val: any) => ({ val, name: 'sources' }), 'superSource': (val: any) => ({ val, name: 'superSources' }), 'talkbackOverSDI': () => ({ val: 0 }) // @todo: should be fixed in atem-connection + }, + customMutate: obj => { + obj.downstreamKeyers = 2 + obj.multiViewers = 2 + obj.upstreamKeyers = 2 + return obj } }, 'FTCD': { diff --git a/src/commands/__tests__/util.ts b/src/commands/__tests__/util.ts index 7186d0565..430f4cc56 100644 --- a/src/commands/__tests__/util.ts +++ b/src/commands/__tests__/util.ts @@ -1,7 +1,7 @@ import { CommandParser } from '../../lib/atemCommandParser' import { ProtocolVersion } from '../../enums' import { IDeserializedCommand, ISerializableCommand } from '../CommandBase' -import { AtemState } from '../../state' +import { createEmptyState } from '../../__tests__/util' export type CommandTestConverterSet = { [key: string]: CommandTestConverter } export interface CommandTestConverter { @@ -72,7 +72,7 @@ export function runTestForCommand (commandParser: CommandParser, commandConverte expect(cmd.properties).toEqual(mutatedCommand) - const state = new AtemState() + const state = createEmptyState() // Ensure state update doesnt error expect(cmd.applyToState(state)).toBeTruthy() }) diff --git a/src/commands/__tests__/v8.0.spec.ts b/src/commands/__tests__/v8.0.spec.ts index 7dbdd0027..9825a54a4 100644 --- a/src/commands/__tests__/v8.0.spec.ts +++ b/src/commands/__tests__/v8.0.spec.ts @@ -28,10 +28,16 @@ const commandConverters: CommandTestConverterSet = { 'auxiliaries': (val: any) => ({ val, name: 'auxilliaries' }), 'dVE': (val: any) => ({ val, name: 'DVEs' }), 'hyperDecks': (val: any) => ({ val, name: 'maxHyperdecks' }), - 'mixEffectBlocks': (val: any) => ({ val, name: 'MEs' }), + 'mixEffectBlocks': (val: any) => ({ val, name: 'mixEffects' }), 'serialPort': (val: any) => ({ val, name: 'serialPorts' }), 'videoSources': (val: any) => ({ val, name: 'sources' }), 'superSource': (val: any) => ({ val, name: 'superSources' }) + }, + customMutate: obj => { + obj.downstreamKeyers = 2 + obj.multiViewers = 2 + obj.upstreamKeyers = 2 + return obj } }, 'SSrc': { diff --git a/src/lib/__tests__/atemSocket.spec.ts b/src/lib/__tests__/atemSocket.spec.ts index 817323e89..8dcb6e90a 100644 --- a/src/lib/__tests__/atemSocket.spec.ts +++ b/src/lib/__tests__/atemSocket.spec.ts @@ -501,7 +501,7 @@ describe('AtemSocket', () => { public static readonly rawName = 'TEST' public deserialize () { - throw new Error('Deserialize failure') + throw new Error('Broken command') } public applyToState (): string[] { throw new Error('Method not implemented.') @@ -533,7 +533,7 @@ describe('AtemSocket', () => { // The second command should have been a success expect(change).toHaveBeenCalledWith(expectedCmd2) - expect(error).toHaveBeenCalledWith(new Error('Deserialize failure')) + expect(error).toHaveBeenCalledWith('Failed to deserialize command: BrokenCommand: Error: Broken command') }) test('receive - thread restart', async () => { diff --git a/src/lib/atemSocket.ts b/src/lib/atemSocket.ts index 38e94aa44..8d374fe21 100644 --- a/src/lib/atemSocket.ts +++ b/src/lib/atemSocket.ts @@ -175,7 +175,7 @@ export class AtemSocket extends EventEmitter { this.emit('commandReceived', cmd) } catch (e) { - this.emit('error', e) + this.emit('error', `Failed to deserialize command: ${cmdConstructor.constructor.name}: ${e}`) } } // TODO - log the unknown command? diff --git a/src/state/info.ts b/src/state/info.ts index cd84d5bef..4ffd2a0bc 100644 --- a/src/state/info.ts +++ b/src/state/info.ts @@ -1,7 +1,7 @@ import { Model, ProtocolVersion } from '../enums' export interface AtemCapabilites { - readonly MEs: number + readonly mixEffects: number readonly sources: number readonly colorGenerators: number readonly auxilliaries: number @@ -13,6 +13,9 @@ export interface AtemCapabilites { readonly stingers: number readonly superSources: number readonly talkbackOverSDI: number + readonly downstreamKeyers: number + readonly multiViewers: number + readonly upstreamKeyers: number } export interface SuperSourceInfo {