Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/quick-tsr/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ export interface Input {
export interface TSRSettings {
multiThreading?: boolean
multiThreadedResolver?: boolean
logCommandReports?: boolean
}

// ------------
Expand Down
65 changes: 37 additions & 28 deletions packages/quick-tsr/src/tsrHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,6 @@ export class TSRHandler {
this.tsr.on('debug', (msg, ...args) => {
console.log('Debug: TSR', msg, ...args)
})
// this.tsr.on('debug', (...args: any[]) => {
// console.log(...args)
// })

this.tsr.on('setTimelineTriggerTime', (_r: TimelineTriggerTimeResult) => {
// TODO
Expand All @@ -90,44 +87,56 @@ export class TSRHandler {
this.tsr.connectionManager.on('connectionRemoved', (deviceId: string) => {
console.log(`Device ${deviceId} removed`)
})
this.tsr.connectionManager.on('connectionEvent:error', (deviceId: string, context: string, err: Error) => {
console.error(`Device ${deviceId} connection error: ${context} = ${err.message}`)
this.tsr.connectionManager.on('error', (ctx: string, err) => {
console.log(`Error: connectionManager (${ctx})`, err)
})
this.tsr.connectionManager.on('warning', (msg: string) => {
console.log('Warning: connectionManager', msg)
})
this.tsr.connectionManager.on('info', (msg: string) => {
console.log('Info: connectionManager', msg)
})
Comment thread
mint-dewit marked this conversation as resolved.
this.tsr.connectionManager.on('debug', (...args: any) => {
console.log('Debug: connectionManager', ...args)
})

this.tsr.connectionManager.on('connectionEvent:connectionChanged', (deviceId: string, status: DeviceStatus) => {
console.log(`Device ${deviceId} status changed: ${JSON.stringify(status)}`)
this.tsr.connectionManager.on('connectionEvent:error', (deviceId: string, context: string, err: Error) => {
console.log(`Device ${deviceId} connection error: ${context} = ${err.message}`)
})
this.tsr.connectionManager.on(
'connectionEvent:slowSentCommand',
(_deviceId: string, _info: SlowSentCommandInfo) => {
// console.log(`Device ${device.deviceId} slow sent command: ${_info}`)
}
)
this.tsr.connectionManager.on(
'connectionEvent:slowFulfilledCommand',
(_deviceId: string, _info: SlowFulfilledCommandInfo) => {
// console.log(`Device ${device.deviceId} slow fulfilled command: ${_info}`)
}
)
this.tsr.connectionManager.on('connectionEvent:commandReport', (deviceId: string, command: any) => {
console.log(`Device ${deviceId} command: ${JSON.stringify(command)}`)
this.tsr.connectionManager.on('connectionEvent:warning', (deviceId: string, warning: string) => {
console.log(`Device ${deviceId} connection warning: ${warning}`)
})
this.tsr.connectionManager.on('connectionEvent:info', (deviceId: string, info: string) => {
console.log(`Device ${deviceId} connection info: ${info}`)
})
Comment thread
mint-dewit marked this conversation as resolved.
this.tsr.connectionManager.on('connectionEvent:debug', (deviceId: string, ...args: any[]) => {
const data = args.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : arg))
console.log(`Device ${deviceId} debug: ${data}`)
})

await this.tsr.init()
this.tsr.connectionManager.on('connectionEvent:connectionChanged', (deviceId: string, status: DeviceStatus) => {
console.log(`Device ${deviceId} status changed: ${JSON.stringify(status)}`)
})

// this._initialized = true
// this._triggerupdateMapping()
// this._triggerupdateTimeline()
// this._triggerupdateDevices()
// this.onSettingsChanged()
// this.logger.debug('tsr init done')
if (tsrSettings.logCommandReports) {
this.tsr.connectionManager.on(
'connectionEvent:slowSentCommand',
(deviceId: string, info: SlowSentCommandInfo) => {
console.log(`Device ${deviceId} slow sent command: ${info}`)
}
)
this.tsr.connectionManager.on(
'connectionEvent:slowFulfilledCommand',
(deviceId: string, info: SlowFulfilledCommandInfo) => {
console.log(`Device ${deviceId} slow fulfilled command: ${info}`)
}
)
this.tsr.connectionManager.on('connectionEvent:commandReport', (deviceId: string, command: any) => {
console.log(`Device ${deviceId} command: ${JSON.stringify(command)}`)
})
}

await this.tsr.init()
}
async destroy(): Promise<void> {
if (this.tsr) return this.tsr.destroy()
Expand Down
3 changes: 2 additions & 1 deletion packages/quick-tsr/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"include": ["src/**/*.ts", "input/**/*.ts"],
"exclude": ["node_modules/**"],
"compilerOptions": {
"types": ["jest", "node"]
"types": ["jest", "node"],
"noUnusedLocals": false
}
}
10 changes: 7 additions & 3 deletions packages/timeline-state-resolver-api/src/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ export interface Device<
sendCommand(command: Command): Promise<void>

applyAddressState?(state: DeviceState, address: string, addressState: AddressState): void
diffAddressStates?(state1: AddressState, state2: AddressState): boolean
diffAddressStates?(state1: AddressState, state2: AddressState | undefined): boolean
diffAddressStates?(state1: AddressState | undefined, state2: AddressState): boolean
addressStateReassertsControl?(oldState: AddressState, newState: AddressState | undefined): boolean
addressStateReassertsControl?(oldState: AddressState | undefined, newState: AddressState): boolean
// -------------------------------------------------------------------
}
Expand Down Expand Up @@ -147,10 +149,12 @@ export interface BaseDeviceAPI<DeviceState, AddressState, Command extends Comman
* The implementation should return true if the contents of the address state differ,
* but not if only the control value differs
*/
diffAddressStates?(state1: AddressState, state2: AddressState): boolean
diffAddressStates?(state1: AddressState, state2: AddressState | undefined): boolean
diffAddressStates?(state1: AddressState | undefined, state2: AddressState): boolean
/**
* Returns true if the
* Returns true if the new state warrants reasserting control over the address
*/
addressStateReassertsControl?(oldState: AddressState, newState: AddressState | undefined): boolean
addressStateReassertsControl?(oldState: AddressState | undefined, newState: AddressState): boolean
/**
* This method takes 2 states and returns a set of device-commands that will
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export interface DeviceCommonOptions {
disable?: boolean
threadUsage?: number
disableSharedHardwareControl?: boolean
syncOnStartup?: boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
"ui:title": "Disable Shared Hardware Control",
"ui:hint": "Some integrations provide a way to read back external state such that non-Sofie actors can control the device too, you can disable this in case the mechanism is causing issues.",
"default": false
},
"syncOnStartup": {
"type": "boolean",
"ui:title": "Sync the device after connecting",
"ui:hint": "Should the device be synced upon first connection. Defaults to true and can only be turned off if \"Shared Hardware Control\" is supported and enabled",
"default": true
Comment thread
mint-dewit marked this conversation as resolved.
}
},
"required": [],
Expand Down
13 changes: 10 additions & 3 deletions packages/timeline-state-resolver/src/integrations/atem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ export class AtemDevice implements Device<AtemDeviceTypes, AtemDeviceState, Atem
this._connectionChanged()
})
this._atem.on('error', (e) => this.context.logger.error('Atem', new Error(e)))
this._atem.on('stateChanged', (state) => {

this._atem.on('stateChanged', (state, changes) => {
if (changes.length === 1 && changes[0] === 'displayClock.currentTime') return

// the external device is communicating something changed, the tracker should be updated (and may fire a "blocked" event if the change is caused by someone else)
updateFromAtemState((addr, addrState) => this.context.setAddressState(addr, addrState), state) // note - improvement can be to update depending on the actual paths that changed

Expand All @@ -86,6 +89,8 @@ export class AtemDevice implements Device<AtemDeviceTypes, AtemDeviceState, Atem
this._connectionChanged()

if (this._atem.state) {
updateFromAtemState((addr, addrState) => this.context.setAddressState(addr, addrState), this._atem.state)

// Do a state diff to get to the desired state
this._protocolVersion = this._atem.state.info.apiVersion
this.context
Expand Down Expand Up @@ -210,7 +215,7 @@ export class AtemDevice implements Device<AtemDeviceTypes, AtemDeviceState, Atem
async sendCommand({ command, context, timelineObjId }: AtemCommandWithContext): Promise<void> {
const cwc: AtemCommandWithContext = {
context,
command,
command: command.map((c) => ({ name: c.constructor.name, ...c })),
timelineObjId,
}
this.context.logger.debug(cwc)
Expand All @@ -231,7 +236,9 @@ export class AtemDevice implements Device<AtemDeviceTypes, AtemDeviceState, Atem
diffAddressStates(state1: AnyAddressState, state2: AnyAddressState): boolean {
return diffAddressStates(state1, state2)
}
addressStateReassertsControl(oldState: AnyAddressState | undefined, newState: AnyAddressState): boolean {
addressStateReassertsControl(oldState: AnyAddressState | undefined, newState: AnyAddressState | undefined): boolean {
if (!newState) return false // undefined incoming state should never reassert

return oldState?.controlValue !== newState.controlValue
}

Expand Down
17 changes: 17 additions & 0 deletions packages/timeline-state-resolver/src/integrations/atem/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SuperSource, TransitionSettings } from 'atem-connection/dist/state/vide
import { DownstreamKeyer } from 'atem-connection/dist/state/video/downstreamKeyers'
import { UpstreamKeyer } from 'atem-connection/dist/state/video/upstreamKeyers'
import { State as DeviceState } from 'atem-state'
import { resolveUpstreamKeyerState } from 'atem-state/dist/resolvers/upstreamKeyers'
import { assertNever, deepMerge } from '../../lib'
import { AtemStateUtil } from 'atem-connection'
import * as _ from 'underscore'
Expand Down Expand Up @@ -251,6 +252,22 @@ export function diffAddressStates(state1: AnyAddressState, state2: AnyAddressSta
return true
if (state1.state.wipe && state2.state.wipe && !_.isEqual(state1.state.wipe, state2.state.wipe)) return true
return false
} else if (state1.type === AddressType.UpStreamKey && state2.type === AddressType.UpStreamKey) {
const output = resolveUpstreamKeyerState(0, [state1.state], [state2.state], {
sources: true,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this diff object the same as the one being used at the point where the command diffing happens?
Perhaps it should be pulled out as a constant that both places can use to keep it in sync?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was thinking about that as well. I mostly just dropped this in because the device state and timeline state aren't strictly/deeply equal even though we should treat them as such. I don't think they have to be using the same options but equally I can't think of a reason why they shouldn't. I'll just pull it out so at least the next person is aware of the usage.

onAir: true,
type: true,
mask: true,
flyKeyframes: 'all',
flyProperties: true,
dveSettings: true,
chromaSettings: true,
advancedChromaSettings: true,
lumaSettings: true,
patternSettings: true,
})

return !!output.length
}

return !_.isEqual(state1.state, state2.state)
Expand Down
23 changes: 17 additions & 6 deletions packages/timeline-state-resolver/src/service/DeviceInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,14 @@ export class DeviceInstanceWrapper extends EventEmitter<DeviceInstanceEvents> {
this._instanceId = Math.floor(Math.random() * 10000)
this._startTime = time

this._logDebug = config.debug ?? this._logDebug

this._updateTimeSync()

if (!config.disableSharedHardwareControl && this._device.diffAddressStates && this._device.applyAddressState) {
this._stateTracker = new StateTracker((state1, state2) =>
this._device.diffAddressStates ? this._device.diffAddressStates(state1, state2) : false
this._stateTracker = new StateTracker(
(state1, state2) => (this._device.diffAddressStates ? this._device.diffAddressStates(state1, state2) : false),
config.syncOnStartup ?? true
)

// for now we just do some logging but in the future we could inform library users so they can react to a device changing
Expand All @@ -126,10 +129,18 @@ export class DeviceInstanceWrapper extends EventEmitter<DeviceInstanceEvents> {
})

// make sure the commands for the next state change are correct:
this._stateTracker.on('deviceUpdated', (ahead) => {
if (ahead) {
this._stateHandler.recalcDiff()
}
let doRecalc = false
this._stateTracker.on('deviceUpdated', (_addr, ahead) => {
if (doRecalc) return
doRecalc = true

// do a little debounce for multiple calls
setImmediate(() => {
doRecalc = false
if (ahead) {
this._stateHandler.recalcDiff()
}
})
Comment thread
mint-dewit marked this conversation as resolved.
})
}

Expand Down
Loading
Loading