From db67a618b7649d039a123d80e9c109e00ce3e94b Mon Sep 17 00:00:00 2001 From: ianshade Date: Wed, 18 Sep 2024 19:18:47 +0200 Subject: [PATCH 1/2] feat(EAV-321): support browser source url in vMix --- .../src/integrations/vmix.ts | 5 +++++ .../vmix/__tests__/connection.spec.ts | 15 +++++++++++++ .../vmix/__tests__/vMixStateDiffer.spec.ts | 22 +++++++++++++++++++ .../vMixTimelineStateConverter.spec.ts | 21 ++++++++++++++++++ .../src/integrations/vmix/connection.ts | 6 +++++ .../src/integrations/vmix/vMixCommands.ts | 6 +++++ .../src/integrations/vmix/vMixStateDiffer.ts | 12 ++++++++++ .../vmix/vMixTimelineStateConverter.ts | 1 + 8 files changed, 88 insertions(+) diff --git a/packages/timeline-state-resolver-types/src/integrations/vmix.ts b/packages/timeline-state-resolver-types/src/integrations/vmix.ts index 1826497c9..01ffbef07 100644 --- a/packages/timeline-state-resolver-types/src/integrations/vmix.ts +++ b/packages/timeline-state-resolver-types/src/integrations/vmix.ts @@ -45,6 +45,7 @@ export enum VMixCommand { LIST_ADD = 'LIST_ADD', LIST_REMOVE_ALL = 'LIST_REMOVE_ALL', RESTART_INPUT = 'RESTART_INPUT', + BROWSER_NAVIGATE = 'BROWSER_NAVIGATE', } export type TimelineContentVMixAny = @@ -188,6 +189,9 @@ export interface TimelineContentVMixInput extends TimelineContentVMixBase { /** If media should start from the beginning or resume from where it left off */ restart?: boolean + + /** The URL for Browser input */ + url?: string } export interface TimelineContentVMixOutput extends TimelineContentVMixBase { @@ -325,4 +329,5 @@ export enum VMixInputType { Flash = 'Flash', PowerPoint = 'PowerPoint', List = 'List', + Browser = 'Browser', } diff --git a/packages/timeline-state-resolver/src/integrations/vmix/__tests__/connection.spec.ts b/packages/timeline-state-resolver/src/integrations/vmix/__tests__/connection.spec.ts index 6c6201fa8..869e1652b 100644 --- a/packages/timeline-state-resolver/src/integrations/vmix/__tests__/connection.spec.ts +++ b/packages/timeline-state-resolver/src/integrations/vmix/__tests__/connection.spec.ts @@ -92,4 +92,19 @@ describe('VMixCommandSender', () => { value: 1.5, }) }) + + it('sends url', async () => { + const { sender, mockConnection } = createTestee() + await sender.sendCommand({ + command: VMixCommand.BROWSER_NAVIGATE, + input: 5, + value: 'https://example.com', + }) + + expect(mockConnection.sendCommandFunction).toHaveBeenCalledTimes(1) + expect(mockConnection.sendCommandFunction).toHaveBeenLastCalledWith('BrowserNavigate', { + input: 5, + value: 'https%3A%2F%2Fexample.com', + }) + }) }) diff --git a/packages/timeline-state-resolver/src/integrations/vmix/__tests__/vMixStateDiffer.spec.ts b/packages/timeline-state-resolver/src/integrations/vmix/__tests__/vMixStateDiffer.spec.ts index f2782ceb9..f1ef3619c 100644 --- a/packages/timeline-state-resolver/src/integrations/vmix/__tests__/vMixStateDiffer.spec.ts +++ b/packages/timeline-state-resolver/src/integrations/vmix/__tests__/vMixStateDiffer.spec.ts @@ -146,4 +146,26 @@ describe('VMixStateDiffer', () => { cropBottom: 0.8, }) }) + + it('sets browser url', () => { + const differ = createTestee() + + const oldState = makeMockFullState() + const newState = makeMockFullState() + + oldState.reportedState.existingInputs['99'] = differ.getDefaultInputState(99) + + newState.reportedState.existingInputs['99'] = differ.getDefaultInputState(99) + const url = 'https://example.com' + newState.reportedState.existingInputs['99'].url = url + + const commands = differ.getCommandsToAchieveState(oldState, newState) + + expect(commands.length).toBe(1) + expect(commands[0].command).toMatchObject({ + command: VMixCommand.BROWSER_NAVIGATE, + input: '99', + value: url, + }) + }) }) diff --git a/packages/timeline-state-resolver/src/integrations/vmix/__tests__/vMixTimelineStateConverter.spec.ts b/packages/timeline-state-resolver/src/integrations/vmix/__tests__/vMixTimelineStateConverter.spec.ts index 66a25af2a..2bb484d82 100644 --- a/packages/timeline-state-resolver/src/integrations/vmix/__tests__/vMixTimelineStateConverter.spec.ts +++ b/packages/timeline-state-resolver/src/integrations/vmix/__tests__/vMixTimelineStateConverter.spec.ts @@ -136,6 +136,27 @@ describe('VMixTimelineStateConverter', () => { expect(result.reportedState.inputsAddedByUsAudio[prefixAddedInput(filePath)]).toBeUndefined() }) + it('supports url', () => { + const converter = createTestee() + const url = 'https://example.com' + const result = converter.getVMixStateFromTimelineState( + wrapInTimelineState({ + inp0: wrapInTimelineObject('inp0', { + deviceType: DeviceType.VMIX, + url, + type: TimelineContentTypeVMix.INPUT, + }), + }), + { + inp0: wrapInMapping({ + mappingType: MappingVmixType.Input, + index: '1', + }), + } + ) + expect(result.reportedState.existingInputs['1'].url).toEqual(url) + }) + // TODO: maybe we can't trust the defaults when adding an input? Make this test pass eventually // it('tracks audio state for mapped inputs added by us', () => { // const converter = createTestee() diff --git a/packages/timeline-state-resolver/src/integrations/vmix/connection.ts b/packages/timeline-state-resolver/src/integrations/vmix/connection.ts index 67ebfed0d..d3ed5a855 100644 --- a/packages/timeline-state-resolver/src/integrations/vmix/connection.ts +++ b/packages/timeline-state-resolver/src/integrations/vmix/connection.ts @@ -260,6 +260,8 @@ export class VMixCommandSender { return this.listRemoveAll(command.input) case VMixCommand.RESTART_INPUT: return this.restart(command.input) + case VMixCommand.BROWSER_NAVIGATE: + return this.browserNavigate(command.input, command.value) default: throw new Error(`vmixAPI: Command ${((command || {}) as any).command} not implemented`) } @@ -467,6 +469,10 @@ export class VMixCommandSender { return this.sendCommandFunction(`Restart`, { input }) } + public async browserNavigate(input: string | number, value: string): Promise { + return this.sendCommandFunction(`BrowserNavigate`, { input, value: encodeURIComponent(value) }) + } + private async sendCommandFunction(func: string, args: SentCommandArgs) { return this.vMixConnection.sendCommandFunction(func, args) } diff --git a/packages/timeline-state-resolver/src/integrations/vmix/vMixCommands.ts b/packages/timeline-state-resolver/src/integrations/vmix/vMixCommands.ts index 9c67d1c21..f3d1fe474 100644 --- a/packages/timeline-state-resolver/src/integrations/vmix/vMixCommands.ts +++ b/packages/timeline-state-resolver/src/integrations/vmix/vMixCommands.ts @@ -203,6 +203,11 @@ export interface VMixStateCommandRestart extends VMixStateCommandBase { command: VMixCommand.RESTART_INPUT input: string | number } +export interface VMixStateCommandBrowserNavigate extends VMixStateCommandBase { + command: VMixCommand.BROWSER_NAVIGATE + input: string | number + value: string +} export type VMixStateCommand = | VMixStateCommandPreviewInput | VMixStateCommandTransition @@ -248,6 +253,7 @@ export type VMixStateCommand = | VMixStateCommandListAdd | VMixStateCommandListRemoveAll | VMixStateCommandRestart + | VMixStateCommandBrowserNavigate export enum CommandContext { None = 'none', diff --git a/packages/timeline-state-resolver/src/integrations/vmix/vMixStateDiffer.ts b/packages/timeline-state-resolver/src/integrations/vmix/vMixStateDiffer.ts index 71948d452..9ba436064 100644 --- a/packages/timeline-state-resolver/src/integrations/vmix/vMixStateDiffer.ts +++ b/packages/timeline-state-resolver/src/integrations/vmix/vMixStateDiffer.ts @@ -78,6 +78,7 @@ export interface VMixInput { layers?: VMixLayers listFilePaths?: string[] restart?: boolean + url?: string } export interface VMixInputAudio { @@ -583,6 +584,17 @@ export class VMixStateDiffer { timelineId: '', }) } + if (input.url !== undefined && oldInput.url !== input.url) { + commands.push({ + command: { + command: VMixCommand.BROWSER_NAVIGATE, + input: key, + value: input.url, + }, + context: CommandContext.None, + timelineId: '', + }) + } return { preTransitionCommands, postTransitionCommands } } diff --git a/packages/timeline-state-resolver/src/integrations/vmix/vMixTimelineStateConverter.ts b/packages/timeline-state-resolver/src/integrations/vmix/vMixTimelineStateConverter.ts index baf9b787d..fc485b259 100644 --- a/packages/timeline-state-resolver/src/integrations/vmix/vMixTimelineStateConverter.ts +++ b/packages/timeline-state-resolver/src/integrations/vmix/vMixTimelineStateConverter.ts @@ -143,6 +143,7 @@ export class VMixTimelineStateConverter { (content.overlays ? this._convertDeprecatedInputOverlays(content.overlays) : undefined), listFilePaths: content.listFilePaths, restart: content.restart, + url: content.url, }, { key: mapping.options.index, filePath: content.filePath }, layerName From f3504a9dd9753e880f88ba3e1c5c71351b5f599c Mon Sep 17 00:00:00 2001 From: ianshade Date: Tue, 26 Nov 2024 21:33:40 +0100 Subject: [PATCH 2/2] chore(EAV-321): fix test for release52 --- .../src/integrations/vmix/__tests__/vMixStateDiffer.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/timeline-state-resolver/src/integrations/vmix/__tests__/vMixStateDiffer.spec.ts b/packages/timeline-state-resolver/src/integrations/vmix/__tests__/vMixStateDiffer.spec.ts index f1ef3619c..ed39f469b 100644 --- a/packages/timeline-state-resolver/src/integrations/vmix/__tests__/vMixStateDiffer.spec.ts +++ b/packages/timeline-state-resolver/src/integrations/vmix/__tests__/vMixStateDiffer.spec.ts @@ -159,7 +159,7 @@ describe('VMixStateDiffer', () => { const url = 'https://example.com' newState.reportedState.existingInputs['99'].url = url - const commands = differ.getCommandsToAchieveState(oldState, newState) + const commands = differ.getCommandsToAchieveState(Date.now(), oldState, newState) expect(commands.length).toBe(1) expect(commands[0].command).toMatchObject({