Skip to content

Commit

Permalink
feat: refactor chef device SOFIE-2494
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed May 29, 2024
1 parent b7ceb69 commit b97a9a8
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 437 deletions.
14 changes: 3 additions & 11 deletions packages/timeline-state-resolver/src/conductor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
DeviceOptionsHyperdeck,
DeviceOptionsPanasonicPTZ,
DeviceOptionsLawo,
DeviceOptionsSofieChef,
} from 'timeline-state-resolver-types'

import { DoOnTime } from './devices/doOnTime'
Expand All @@ -48,7 +49,6 @@ import { SisyfosMessageDevice, DeviceOptionsSisyfosInternal } from './integratio
import { SingularLiveDevice, DeviceOptionsSingularLiveInternal } from './integrations/singularLive'
import { VMixDevice, DeviceOptionsVMixInternal } from './integrations/vmix'
import { VizMSEDevice, DeviceOptionsVizMSEInternal } from './integrations/vizMSE'
import { DeviceOptionsSofieChefInternal, SofieChefDevice } from './integrations/sofieChef'
import { TelemetricsDevice } from './integrations/telemetrics'
import { TriCasterDevice, DeviceOptionsTriCasterInternal } from './integrations/tricaster'
import { DeviceOptionsMultiOSCInternal, MultiOSCMessageDevice } from './integrations/multiOsc'
Expand Down Expand Up @@ -564,15 +564,6 @@ export class Conductor extends EventEmitter<ConductorEvents> {
getCurrentTime,
threadedClassOptions
)
case DeviceType.SOFIE_CHEF:
return DeviceContainer.create<DeviceOptionsSofieChefInternal, typeof SofieChefDevice>(
'../../dist/integrations/sofieChef/index.js',
'SofieChefDevice',
deviceId,
deviceOptions,
getCurrentTime,
threadedClassOptions
)
case DeviceType.TRICASTER:
return DeviceContainer.create<DeviceOptionsTriCasterInternal, typeof TriCasterDevice>(
'../../dist/integrations/tricaster/index.js',
Expand Down Expand Up @@ -601,6 +592,7 @@ export class Conductor extends EventEmitter<ConductorEvents> {
case DeviceType.OSC:
case DeviceType.PANASONIC_PTZ:
case DeviceType.SHOTOKU:
case DeviceType.SOFIE_CHEF:
case DeviceType.TCPSEND:
case DeviceType.QUANTEL: {
ensureIsImplementedAsService(deviceOptions.type)
Expand Down Expand Up @@ -1518,7 +1510,7 @@ export type DeviceOptionsAnyInternal =
| DeviceOptionsOSC
| DeviceOptionsMultiOSCInternal
| DeviceOptionsSisyfosInternal
| DeviceOptionsSofieChefInternal
| DeviceOptionsSofieChef
| DeviceOptionsQuantel
| DeviceOptionsSingularLiveInternal
| DeviceOptionsVMixInternal
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { diffStates } from '../diffStates'
import { SofieChefCommandWithContext, SofieChefState } from '..'
import { ReceiveWSMessageType } from '../api'

describe('Diff States', () => {
test('Simple diff against undefined state', async () => {
const state1: SofieChefState = {
windows: {
test: {
url: 'http://test.com',
urlTimelineObjId: 'abc',
},
},
}

const commands = diffStates(undefined, state1, {})

expect(commands).toHaveLength(1)
expect(commands[0]).toEqual({
command: {
msgId: 0,
type: ReceiveWSMessageType.PLAYURL,
url: 'http://test.com',
windowId: 'test',
},
context: 'added',
timelineObjId: 'abc',
} satisfies SofieChefCommandWithContext)
})

test('Simple diff against another state', async () => {
const state1: SofieChefState = {
windows: {
test: {
url: 'http://test.com',
urlTimelineObjId: 'abc',
},
two: {
url: 'http://two.com',
urlTimelineObjId: 'def',
},
three: {
url: 'http://three.com',
urlTimelineObjId: 'ghi',
},
},
}

const state2: SofieChefState = {
windows: {
two: {
url: 'http://two.com/2',
urlTimelineObjId: '012',
},
another: {
url: 'http://another.com',
urlTimelineObjId: '345',
},
three: {
url: 'http://three.com',
urlTimelineObjId: 'ghi',
},
},
}

const commands = diffStates(state1, state2, {})

expect(commands).toHaveLength(3)
expect(commands[0]).toEqual({
command: {
msgId: 0,
type: ReceiveWSMessageType.PLAYURL,
url: 'http://two.com/2',
windowId: 'two',
},
context: 'changed',
timelineObjId: '012',
} satisfies SofieChefCommandWithContext)
expect(commands[1]).toEqual({
command: {
msgId: 0,
type: ReceiveWSMessageType.PLAYURL,
url: 'http://another.com',
windowId: 'another',
},
context: 'added',
timelineObjId: '345',
} satisfies SofieChefCommandWithContext)
expect(commands[2]).toEqual({
command: {
msgId: 0,
type: ReceiveWSMessageType.STOP,
windowId: 'test',
},
context: 'removed',
timelineObjId: 'abc',
} satisfies SofieChefCommandWithContext)
})
})
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
import { Conductor } from '../../../conductor'
import { SofieChefDevice } from '..'
import {
Mappings,
DeviceType,
Mapping,
SomeMappingSofieChef,
TimelineContentTypeSofieChef,
StatusCode,
MappingSofieChefType,
} from 'timeline-state-resolver-types'
import { StatusCode } from 'timeline-state-resolver-types'
import { MockTime } from '../../../__tests__/mockTime'
import { ThreadedClass } from 'threadedclass'
import { getMockCall } from '../../../__tests__/lib'
import * as WebSocket from '../../../__mocks__/ws'
import { literal } from '../../../lib'
import { SendWSMessageAny, SendWSMessageType, StatusCode as ChefStatusCode } from '../api'
import { getDeviceContext } from '../../__tests__/testlib'

describe('SofieChef', () => {
jest.mock('ws', () => WebSocket)
Expand All @@ -26,31 +16,6 @@ describe('SofieChef', () => {
})

test('Status & reconnection', async () => {
let device: any = undefined
const commandReceiver0: any = jest.fn((...args) => {
// pipe through the command
return device._defaultCommandReceiver(...args)
// return Promise.resolve()
})
const myLayerMapping0: Mapping<SomeMappingSofieChef> = {
device: DeviceType.SOFIE_CHEF,
deviceId: 'chef0',
options: {
mappingType: MappingSofieChefType.Window,
windowId: 'window0',
},
}
const myLayerMapping: Mappings = {
myLayer0: myLayerMapping0,
}

const myConductor = new Conductor({
multiThreadedResolver: false,
getCurrentTime: mockTime.getCurrentTime,
})
const errorHandler = jest.fn()
myConductor.on('error', errorHandler)

WebSocket.mockConstructor((ws: WebSocket) => {
// @ts-ignore mock
// ws.mockReplyFunction((message) => {
Expand Down Expand Up @@ -85,32 +50,18 @@ describe('SofieChef', () => {
})
})

await myConductor.init()
await myConductor.addDevice('chef0', {
type: DeviceType.SOFIE_CHEF,
options: {
address: 'ws://127.0.0.1',
},
commandReceiver: commandReceiver0,
const device = new SofieChefDevice(getDeviceContext())
await device.init({
address: 'ws://127.0.0.1',
})
myConductor.setTimelineAndMappings([], myLayerMapping)

const wsInstances = WebSocket.getMockInstances()
expect(wsInstances).toHaveLength(1)
const wsInstance = wsInstances[0]

await mockTime.advanceTimeToTicks(10100)

const deviceContainer = myConductor.getDevice('chef0')
device = deviceContainer!.device as ThreadedClass<SofieChefDevice>
const chefDevice = deviceContainer!.device as ThreadedClass<SofieChefDevice>

// Check that no commands has been scheduled:
expect(await device.queue).toHaveLength(0)

await mockTime.advanceTimeTicks(100)

expect(await chefDevice.getStatus()).toMatchObject({
expect(device.getStatus()).toMatchObject({
statusCode: StatusCode.GOOD,
})

Expand All @@ -137,7 +88,7 @@ describe('SofieChef', () => {

await mockTime.advanceTimeTicks(100)

expect(await chefDevice.getStatus()).toMatchObject({
expect(device.getStatus()).toMatchObject({
statusCode: StatusCode.BAD,
messages: ['Window 5: whoopsie'],
})
Expand All @@ -147,7 +98,7 @@ describe('SofieChef', () => {

await mockTime.advanceTimeTicks(100)

expect(await chefDevice.getStatus()).toMatchObject({
expect(device.getStatus()).toMatchObject({
statusCode: StatusCode.BAD,
messages: ['Not connected'],
})
Expand All @@ -157,133 +108,9 @@ describe('SofieChef', () => {

await mockTime.advanceTimeTicks(5000)

expect(await chefDevice.getStatus()).toMatchObject({
expect(device.getStatus()).toMatchObject({
statusCode: StatusCode.GOOD,
messages: [],
})
})
test('Play & stop URL', async () => {
let device: any = undefined
const commandReceiver0: any = jest.fn((...args) => {
// pipe through the command
return device._defaultCommandReceiver(...args)
// return Promise.resolve()
})
const myLayerMapping0: Mapping<SomeMappingSofieChef> = {
device: DeviceType.SOFIE_CHEF,
deviceId: 'chef0',
options: {
mappingType: MappingSofieChefType.Window,
windowId: 'window0',
},
}
const myLayerMapping: Mappings = {
myLayer0: myLayerMapping0,
}

const myConductor = new Conductor({
multiThreadedResolver: false,
getCurrentTime: mockTime.getCurrentTime,
})
const errorHandler = jest.fn()
myConductor.on('error', errorHandler)

WebSocket.mockConstructor((ws: WebSocket) => {
setImmediate(() => {
ws.mockSetConnected(true)
})
})

await myConductor.init()
await myConductor.addDevice('chef0', {
type: DeviceType.SOFIE_CHEF,
options: {
address: 'ws://127.0.0.1',
},
commandReceiver: commandReceiver0,
})
myConductor.setTimelineAndMappings([], myLayerMapping)

const wsInstances = WebSocket.getMockInstances()
expect(wsInstances).toHaveLength(1)

await mockTime.advanceTimeToTicks(10100)

const deviceContainer = myConductor.getDevice('chef0')
device = deviceContainer!.device as ThreadedClass<SofieChefDevice>

// Check that no commands has been scheduled:
expect(await device.queue).toHaveLength(0)

myConductor.setTimelineAndMappings(
[
{
id: 'url0',
enable: {
start: 11000,
end: 20000,
},
layer: 'myLayer0',
content: {
deviceType: DeviceType.SOFIE_CHEF,
type: TimelineContentTypeSofieChef.URL,
url: 'http://google.com',
},
keyframes: [
{
id: 'kf0',
enable: {
start: 4000, // 15000
},
content: {
url: 'http://yahoo.com',
},
},
],
},
],
myLayerMapping
)

await mockTime.advanceTimeToTicks(10990)
expect(commandReceiver0).toHaveBeenCalledTimes(0)

await mockTime.advanceTimeToTicks(11100)
expect(commandReceiver0).toHaveBeenCalledTimes(1)
expect(getMockCall(commandReceiver0, 0, 1)).toMatchObject({
content: {
type: 'playurl',
url: 'http://google.com',
windowId: 'window0',
},
context: 'added',
timelineObjId: 'url0',
})

commandReceiver0.mockReset()

await mockTime.advanceTimeToTicks(18500)
expect(commandReceiver0).toHaveBeenCalledTimes(1)
expect(getMockCall(commandReceiver0, 0, 1)).toMatchObject({
content: {
type: 'playurl',
url: 'http://yahoo.com',
windowId: 'window0',
},
context: 'changed',
timelineObjId: 'url0',
})

commandReceiver0.mockReset()
await mockTime.advanceTimeToTicks(20100)
expect(commandReceiver0).toHaveBeenCalledTimes(1)
expect(getMockCall(commandReceiver0, 0, 1)).toMatchObject({
content: {
type: 'stop',
windowId: 'window0',
},
context: 'removed',
timelineObjId: 'url0',
})
})
})
Loading

0 comments on commit b97a9a8

Please sign in to comment.