Skip to content

Commit

Permalink
feat: upgrade singular.live to API v2
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Van Camp committed Apr 5, 2023
1 parent fbcd4ff commit 2bb5c4d
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 65 deletions.
11 changes: 4 additions & 7 deletions packages/timeline-state-resolver-types/src/singularLive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,11 @@ export interface TimelineContentSingularLiveBase {

export interface TimelineContentSingularLiveComposition extends TimelineContentSingularLiveBase {
type: TimelineContentTypeSingularLive.COMPOSITION

animation?: SingularCompositionAnimation
controlNode: SingularCompositionControlNode
}
export interface SingularCompositionAnimation {
action: 'jump' | 'play'
// stage: string
}
export interface SingularCompositionControlNode {
payload: { [key: string]: string }
/** The animation state that the node should be in. I.e. "In", "Out", etc. */
state?: string
/** The data that should be consumed by the node. Could be text, colors, etc. */
payload?: { [key: string]: string }
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ describe('Singular.Live', () => {
deviceType: DeviceType.SINGULAR_LIVE,
type: TimelineContentTypeSingularLive.COMPOSITION,
controlNode: {
state: 'In',
payload: {
Name: 'Thomas',
Title: 'Foreperson',
Expand All @@ -75,23 +76,22 @@ describe('Singular.Live', () => {
expect(commandReceiver0).toHaveBeenCalledTimes(0)
await mockTime.advanceTimeToTicks(11100)

expect(commandReceiver0).toHaveBeenCalledTimes(2)
expect(commandReceiver0).toHaveBeenCalledTimes(1)
expect(commandReceiver0).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
compositionName: 'Lower Third',
controlNode: {
payload: {
Name: 'Thomas',
Title: 'Foreperson',
},
subCompositionName: 'Lower Third',
state: 'In',
payload: {
Name: 'Thomas',
Title: 'Foreperson',
},
}),
expect.anything(),
expect.stringContaining('obj0')
)
expect(getMockCall(commandReceiver0, 0, 2)).toMatch(/added/) // context
await mockTime.advanceTimeToTicks(16000)
expect(commandReceiver0).toHaveBeenCalledTimes(3)
expect(commandReceiver0).toHaveBeenCalledTimes(2)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
TimelineContentTypeSingularLive,
MappingSingularLive,
DeviceOptionsSingularLive,
SingularCompositionAnimation,
SingularCompositionControlNode,
Mappings,
TSRTimelineContent,
Expand All @@ -25,21 +24,13 @@ export type CommandReceiver = (
timelineObjId: string
) => Promise<any>

export interface SingularLiveAnimationCommandContent extends SingularLiveCommandContent {
animation: {
action: 'play' | 'jump'
to: 'In' | 'Out'
}
}

export interface SingularLiveControlNodeCommandContent extends SingularLiveCommandContent {
controlNode: {
payload: { [key: string]: string }
}
state?: string
payload?: { [controlNodeField: string]: string }
}

export interface SingularLiveCommandContent {
compositionName: string
subCompositionName: string
}

interface Command {
Expand All @@ -53,7 +44,6 @@ export type CommandContext = string

export interface SingularComposition {
timelineObjId: string
animation: SingularCompositionAnimation
controlNode: SingularCompositionControlNode
}

Expand All @@ -63,7 +53,7 @@ export interface SingularLiveState {
}
}

const SINGULAR_LIVE_API = 'https://app.singular.live/apiv1/control/'
const SINGULAR_LIVE_API = 'https://app.singular.live/apiv2/controlapps/'

/**
* This is a Singular.Live device, it talks to a Singular.Live App Instance using an Access Token
Expand Down Expand Up @@ -188,9 +178,7 @@ export class SingularLiveDevice extends DeviceWithState<SingularLiveState, Devic
if (content.type === TimelineContentTypeSingularLive.COMPOSITION) {
singularState.compositions[mapping.compositionName] = {
timelineObjId: tlObject.id,

controlNode: content.controlNode,
animation: content.animation || { action: 'play' },
}
}
}
Expand Down Expand Up @@ -236,27 +224,13 @@ export class SingularLiveDevice extends DeviceWithState<SingularLiveState, Devic
const oldComposition = oldSingularLiveState.compositions[compositionName]
if (!oldComposition) {
// added!
commands.push({
timelineObjId: composition.timelineObjId,
commandName: 'added',
content: literal<SingularLiveAnimationCommandContent>({
compositionName: compositionName,
animation: {
action: composition.animation.action,
to: 'In',
},
}),
context: `added: ${composition.timelineObjId}`,
layer: compositionName,
})
commands.push({
timelineObjId: composition.timelineObjId,
commandName: 'added',
content: literal<SingularLiveControlNodeCommandContent>({
compositionName: compositionName,
controlNode: {
payload: composition.controlNode.payload,
},
subCompositionName: compositionName,
state: composition.controlNode.state,
payload: composition.controlNode.payload,
}),
context: `added: ${composition.timelineObjId}`,
layer: compositionName,
Expand All @@ -269,10 +243,9 @@ export class SingularLiveDevice extends DeviceWithState<SingularLiveState, Devic
timelineObjId: composition.timelineObjId,
commandName: 'changed',
content: literal<SingularLiveControlNodeCommandContent>({
compositionName: compositionName,
controlNode: {
payload: composition.controlNode.payload,
},
subCompositionName: compositionName,
state: composition.controlNode.state,
payload: composition.controlNode.payload,
}),
context: `changed: ${composition.timelineObjId} (previously: ${oldComposition.timelineObjId})`,
layer: compositionName,
Expand All @@ -288,12 +261,9 @@ export class SingularLiveDevice extends DeviceWithState<SingularLiveState, Devic
commands.push({
timelineObjId: composition.timelineObjId,
commandName: 'removed',
content: literal<SingularLiveAnimationCommandContent>({
compositionName: compositionName,
animation: {
action: composition.animation.action,
to: 'Out',
},
content: literal<SingularLiveControlNodeCommandContent>({
subCompositionName: compositionName,
state: 'Out',
}),
context: `removed: ${composition.timelineObjId}`,
layer: compositionName,
Expand All @@ -302,9 +272,9 @@ export class SingularLiveDevice extends DeviceWithState<SingularLiveState, Devic
})
return commands
.sort((a, b) =>
(a.content as any).controlNode && !(b.content as any).controlNode
(a.content as any).state && !(b.content as any).state
? 1
: !(a.content as any).controlNode && (b.content as any).controlNode
: !(a.content as any).state && (b.content as any).state
? -1
: 0
)
Expand All @@ -323,28 +293,28 @@ export class SingularLiveDevice extends DeviceWithState<SingularLiveState, Devic
}
this.emitDebug(cwc)

const url = SINGULAR_LIVE_API + this._accessToken
const url = SINGULAR_LIVE_API + this._accessToken + '/control'

return new Promise<void>((resolve, reject) => {
const handleResponse = (error, response) => {
if (error) {
this.emit('error', `SingularLive.response error ${cmd.compositionName} (${context}`, error)
this.emit('error', `SingularLive.response error ${cmd.subCompositionName} (${context}`, error)
reject(error)
} else if (response.statusCode === 200) {
this.emitDebug(
`SingularLive: ${cmd.compositionName}: Good statuscode response on url "${url}": ${response.statusCode} (${context})`
`SingularLive: ${cmd.subCompositionName}: Good statuscode response on url "${url}": ${response.statusCode} (${context})`
)
resolve()
} else {
this.emit(
'warning',
`SingularLive: ${cmd.compositionName}: Bad statuscode response on url "${url}": ${response.statusCode} (${context})`
`SingularLive: ${cmd.subCompositionName}: Bad statuscode response on url "${url}": ${response.statusCode} (${context})`
)
resolve()
}
}

request.put(url, { json: [cmd] }, handleResponse)
request.patch(url, { json: [cmd] }, handleResponse)
}).catch((error) => {
this.emit('commandError', error, cwc)
})
Expand Down

0 comments on commit 2bb5c4d

Please sign in to comment.