Skip to content

Commit

Permalink
Merge pull request #24 from nrkno/fix/sofie-3648/streamdeck-detection…
Browse files Browse the repository at this point in the history
…-slow

fix(Stream Deck): update elgato-stream-deck library to resolve an issue with slow discovery of HID devices
  • Loading branch information
jstarpl authored Jan 24, 2025
2 parents 65e4e5b + 71f485e commit b87b0d8
Show file tree
Hide file tree
Showing 10 changed files with 570 additions and 291 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"unitci": "yarn workspaces foreach --all run unitci",
"dev:manager": "cd packages/input-manager && run build:watch",
"dev:gateway": "nodemon --config nodemon.json",
"dev": "concurrently \"yarn dev:manager\" \"yarn dev:gateway\"",
"dev": "concurrently \"yarn dev:manager\" --handle-input \"yarn dev:gateway\"",
"release:set-version": "lerna version --exact --no-changelog --no-git-tag-version --no-push --yes",
"release:bump-release": "lerna version --exact --conventional-commits --conventional-graduate --tag-version-prefix='v' --no-push",
"release:bump-prerelease": "lerna version --exact --conventional-commits --conventional-prerelease --tag-version-prefix='v' --no-push",
Expand All @@ -32,7 +32,7 @@
"do:build-win32": "rimraf deploy && yarn install && yarn build && yarn build-win32 && yarn copy-assets && yarn sign-executables && yarn zip-deploy",
"do:build-win32:ci": "rimraf deploy && yarn install && yarn build && yarn build-win32 && yarn copy-assets",
"verify:build-win32": "node scripts/verify-build-win32.mjs",
"build-win32": "pkg -t node18-win --public ./packages/input-gateway",
"build-win32": "pkg -t node22-win --public ./packages/input-gateway",
"sign-executables": "node scripts/sign-executables.mjs",
"copy-assets": "node scripts/copy-assets.mjs",
"zip-deploy": "node scripts/zip-deploy.js",
Expand All @@ -57,13 +57,13 @@
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"type-fest": "^4.18.2",
"typescript": "~5.1.3",
"typescript": "5.6.3",
"zip-a-folder": "^3.1.6"
},
"prettier": "@sofie-automation/code-standard-preset/.prettierrc.json",
"packageManager": "[email protected]",
"dependencies": {
"@yao-pkg/pkg": "^5.11.5"
"@yao-pkg/pkg": "6.2.0"
},
"resolutions": {
"node-hid": "npm:@julusian/[email protected]"
Expand Down
16 changes: 8 additions & 8 deletions packages/input-gateway/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"scripts": {
"start": "node dist/index.js",
"build": "run -T -B rimraf dist && run build:main",
"buildstart": "run build && run start -id localDevInp",
"buildstart": "run build && run start -id localDevInp -token localDevInp",
"build:main": "run -T -B tsc -p tsconfig.build.json",
"lint:raw": "run -T g:eslint --ext .ts --ext .js --ext .tsx --ext .jsx --ignore-pattern dist",
"lint": "run lint:raw .",
Expand Down Expand Up @@ -72,14 +72,14 @@
"production"
],
"dependencies": {
"@esm2cjs/p-queue": "7.3.0",
"@sofie-automation/input-manager": "0.3.0",
"@sofie-automation/server-core-integration": "1.51.0-nightly-release51-20240524-100952-fa306dc.0",
"@sofie-automation/shared-lib": "1.51.0-nightly-release51-20240524-100952-fa306dc.0",
"@sofie-automation/server-core-integration": "1.51.6",
"@sofie-automation/shared-lib": "1.51.6",
"debug": "^4.3.4",
"eventemitter3": "^4.0.7",
"p-all": "^3.0.0",
"p-queue": "^6.6.2",
"p-timeout": "^3.2.0",
"eventemitter3": "5.0.1",
"p-all": "5.0.0",
"p-timeout": "6.1.4",
"sprintf-js": "^1.1.3",
"tslib": "^2.6.2",
"underscore": "^1.13.6",
Expand All @@ -98,7 +98,7 @@
"./package.json"
],
"targets": [
"node18-win"
"node22-win"
],
"outputPath": "deploy"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/input-gateway/src/coreHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export class CoreHandler {
this.logger.info('Loglevel: ' + this.logger.level)
}

this.logger.info('Changed PeripheralDevice: ' + JSON.stringify(device))
this.logger.info('Changed PeripheralDevice')
if (this._onChanged) this._onChanged()
}
}
Expand Down
8 changes: 2 additions & 6 deletions packages/input-gateway/src/inputManagerHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
PeripheralDevicePubSubCollectionsNames,
} from '@sofie-automation/server-core-integration'
import { literal, sleep } from '@sofie-automation/shared-lib/dist/lib/lib'
import PQueue from 'p-queue'
import PQueue from '@esm2cjs/p-queue'
import { InputGatewaySettings } from './generated/options'

export type SetProcessState = (processName: string, comments: string[], status: StatusCode) => void
Expand Down Expand Up @@ -140,8 +140,6 @@ export class InputManagerHandler {
async initInputManager(settings: DeviceSettings): Promise<void> {
this.#logger.info('Initializing Input Manager with the following settings:')

this.#logger.info(JSON.stringify(settings))

this.#deviceSettings = settings

this.#inputManager = await this.#createInputManager(settings)
Expand Down Expand Up @@ -650,9 +648,7 @@ export class InputManagerHandler {
contentLayerLongName = previewedAdlibs[0].sourceLayerName?.name
contentLayerShortName = previewedAdlibs[0].sourceLayerName?.abbreviation
contentLabel = previewedAdlibs.map((adlib) => InputManagerHandler.getStringLabel(adlib.label)).join(', ')
contentTypes = previewedAdlibs
.map((adlib) => adlib.sourceLayerType)
.filter((a) => a !== undefined) as SourceLayerType[]
contentTypes = previewedAdlibs.map((adlib) => adlib.sourceLayerType).filter((a) => a !== undefined)
styleClassNames = previewedAdlibs[0].styleClassNames
}
}
Expand Down
8 changes: 4 additions & 4 deletions packages/input-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@
"production"
],
"dependencies": {
"@elgato-stream-deck/node": "^6.2.0",
"@elgato-stream-deck/node": "7.1.2",
"@julusian/jpeg-turbo": "^2.1.0",
"easymidi": "^3.1.0",
"eventemitter3": "^4.0.7",
"eventemitter3": "5.0.1",
"fold-to-ascii": "^5.0.1",
"lru-cache": "^10.4.2",
"lru-cache": "11.0.2",
"osc": "^2.4.4",
"skia-canvas": "^1.0.2",
"skia-canvas": "2.0.1",
"xkeys": "^3.1.1"
},
"peerDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/input-manager/src/generated/streamdeck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export interface StreamdeckStylePreset {
fontWeight?: 'normal' | 'bold'
fontWidth?: 'normal' | 'narrow'
fontStyle?: 'normal' | 'italic'
color?: boolean & string
color?: string
textStrokeColor?: string
textShadowColor?: string
textShadowOffset?: number
Expand All @@ -43,7 +43,7 @@ export interface StreamdeckStylePreset {
| 'left bottom'
| 'center bottom'
| 'right bottom'
textTransform?: 'capitalize' | 'uppercase' | 'lowercase'
textTransform?: 'none' | 'capitalize' | 'uppercase' | 'lowercase'
lineClamp?: number
inlineBackground?: string
inlineBackgroundPadding?: string
Expand Down
2 changes: 1 addition & 1 deletion packages/input-manager/src/inputManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class InputManager extends EventEmitter<DeviceEvents> {
}

private refreshDevicesInterval = (): void => {
this.#logger.silly(`Refreshing devices... ${this.#refreshRunning}`)
this.#logger.silly(`Refreshing devices... Is already running: ${this.#refreshRunning}`)
if (this.#refreshRunning === true) return

this.#refreshRunning = true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import type { StreamDeckButtonControlDefinition, StreamDeckEncoderControlDefinition } from '@elgato-stream-deck/node'
import { Symbols } from '../../../lib'
import { sleep } from '@sofie-automation/shared-lib/dist/lib/lib'

const mockBitmapFeedbackFactory = {
init: jest.fn(),
getBitmap: jest.fn((feedback: any, _width: number, _height: number, isDown: boolean) => {
Expand All @@ -17,20 +21,41 @@ const mockStreamDeckList = [
},
]

let mockListeners: Record<string, (key: number) => void> = {}
let mockListeners: Record<
string,
(control: StreamDeckButtonControlDefinition | StreamDeckEncoderControlDefinition) => void
> = {}

const mockStreamDeck = {
ICON_SIZE: 96,
addListener: jest.fn((event: string, listener: (key: number) => void) => {
mockListeners[event] = listener
}),
addListener: jest.fn(
(
event: string,
listener: (control: StreamDeckButtonControlDefinition | StreamDeckEncoderControlDefinition) => void
) => {
mockListeners[event] = listener
}
),
clearKey: jest.fn(),
fillKeyBuffer: jest.fn(),
fillEncoderLcd: jest.fn(),
fillLcd: jest.fn(),
clearPanel: jest.fn(),
close: jest.fn(),
checkValidKeyIndex: jest.fn(),
setBrightness: jest.fn(async () => Promise.resolve()),
CONTROLS: [
{
type: 'button' as const,
feedbackType: 'lcd' as const,
pixelSize: {
height: 96,
width: 96,
},
index: 1,
hidIndex: 0,
row: 0,
column: 0,
},
],
}

jest.mock('@elgato-stream-deck/node', () => ({
Expand All @@ -48,10 +73,12 @@ jest.mock('@elgato-stream-deck/node', () => ({
},
}))

function mockTriggerControl(event: 'up' | 'down') {
mockListeners[event](mockStreamDeck.CONTROLS[0])
}

import { StreamDeckDevice } from '../index'
import { MockLogger } from '../../../__mocks__/logger'
import { Symbols } from '../../../lib'
import { sleep } from '@sofie-automation/shared-lib/dist/lib/lib'

describe('Stream Deck', () => {
async function connectToMockStreamDeck() {
Expand Down Expand Up @@ -80,7 +107,7 @@ describe('Stream Deck', () => {
const triggerHandler = jest.fn()
device.on('trigger', triggerHandler)

mockListeners['down'](1)
mockTriggerControl('down')

expect(triggerHandler).toHaveBeenCalledTimes(1)
expect(device.getNextTrigger()).toMatchObject({
Expand All @@ -89,7 +116,7 @@ describe('Stream Deck', () => {
expect(device.getNextTrigger()).toBeUndefined() // No more triggers to send
triggerHandler.mockClear()

mockListeners['up'](1)
mockTriggerControl('up')

expect(triggerHandler).toHaveBeenCalledTimes(1)
expect(device.getNextTrigger()).toMatchObject({
Expand Down Expand Up @@ -135,15 +162,15 @@ describe('Stream Deck', () => {
expect(mockStreamDeck.fillKeyBuffer.mock.calls[1][1]).toMatch(/Mock Action/)
expect(mockStreamDeck.fillKeyBuffer.mock.calls[1][1]).toMatch(/isDown: false/)

mockListeners['down'](1)
mockTriggerControl('down')
await sleep(2) // the streamdeck needs some time to update

expect(mockStreamDeck.fillKeyBuffer).toHaveBeenCalledTimes(3)
expect(mockStreamDeck.fillKeyBuffer.mock.calls[2][0]).toBe(1)
expect(mockStreamDeck.fillKeyBuffer.mock.calls[2][1]).toMatch(/Mock Action/)
expect(mockStreamDeck.fillKeyBuffer.mock.calls[2][1]).toMatch(/isDown: true/)

mockListeners['up'](1)
mockTriggerControl('up')
await sleep(2)

expect(mockStreamDeck.fillKeyBuffer).toHaveBeenCalledTimes(4)
Expand Down
Loading

0 comments on commit b87b0d8

Please sign in to comment.