From b1744f217b96bbc6d8d84276c1764e1c170bc71a Mon Sep 17 00:00:00 2001 From: duncte123 Date: Sun, 14 Jan 2024 19:34:11 +0100 Subject: [PATCH 01/16] WIP converstion to socket v5 --- extension/obs/src/index.ts | 21 ++++------ package-lock.json | 79 ++++++++++++++++++++++++-------------- package.json | 2 +- 3 files changed, 60 insertions(+), 42 deletions(-) diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index 859815c..c21c15a 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -1,7 +1,7 @@ import type NodeCGTypes from '@nodecg/types'; import clone from 'clone'; import { EventEmitter } from 'events'; -import ObsWebsocketJs from 'obs-websocket-js'; +import OBSWebSocket from 'obs-websocket-js'; import { findBestMatch } from 'string-similarity'; import { OBS as OBSTypes } from '../../../types'; @@ -15,7 +15,7 @@ interface OBS { class OBS extends EventEmitter { private nodecg: NodeCGTypes.ServerAPI; private config: OBSTypes.Config; - conn = new ObsWebsocketJs(); + conn = new OBSWebSocket(); currentScene: string | undefined; sceneList: string [] = []; connected = false; @@ -51,13 +51,8 @@ class OBS extends EventEmitter { this.emit('sceneListChanged', this.sceneList); }); - this.conn.on('StreamStarted', () => { - this.streaming = true; - this.emit('streamingStatusChanged', this.streaming, !this.streaming); - }); - - this.conn.on('StreamStopped', () => { - this.streaming = false; + this.conn.on('StreamStateChanged', ({ outputActive }) => { + this.streaming = outputActive; this.emit('streamingStatusChanged', this.streaming, !this.streaming); }); @@ -153,7 +148,7 @@ class OBS extends EventEmitter { try { const scene = this.findScene(name); if (scene) { - await this.conn.send('SetCurrentScene', { 'scene-name': scene }); + await this.conn.call('SetCurrentScene', { 'scene-name': scene }); } else { throw new Error('Scene could not be found'); } @@ -180,7 +175,7 @@ class OBS extends EventEmitter { throw new Error('No connection available'); } try { - const resp = await this.conn.send('GetSourceSettings', { sourceName }); + const resp = await this.conn.call('GetSourceSettings', { sourceName }); return resp; } catch (err) { this.nodecg.log.warn(`[OBS] Cannot get source settings [${sourceName}]`); @@ -203,7 +198,7 @@ class OBS extends EventEmitter { throw new Error('No connection available'); } try { - await this.conn.send('SetSourceSettings', { + await this.conn.call('SetSourceSettings', { sourceName, sourceType, sourceSettings, @@ -248,7 +243,7 @@ class OBS extends EventEmitter { } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: Typings say we need to specify more than we actually do. - await this.conn.send('SetSceneItemProperties', { + await this.conn.call('SetSceneItemProperties', { 'scene-name': scene, item: { name: item }, visible: visible ?? true, diff --git a/package-lock.json b/package-lock.json index 74ca329..02114e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "get-video-duration": "3.0.2", "lodash": "^4.17.21", "node-fetch": "^2.7.0", - "obs-websocket-js": "^4.0.3", + "obs-websocket-js": "^5.0.4", "osc": "^2.4.4", "string-similarity": "^4.0.4", "tiny-typed-emitter": "^2.1.0", @@ -194,6 +194,14 @@ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "dev": true }, + "node_modules/@msgpack/msgpack": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", + "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==", + "engines": { + "node": ">= 10" + } + }, "node_modules/@serialport/binding-mock": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz", @@ -882,6 +890,11 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, "node_modules/d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -1034,6 +1047,11 @@ "es5-ext": "~0.10.14" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, "node_modules/execa": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", @@ -1432,9 +1450,9 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", "peerDependencies": { "ws": "*" } @@ -1889,14 +1907,31 @@ } }, "node_modules/obs-websocket-js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/obs-websocket-js/-/obs-websocket-js-4.0.3.tgz", - "integrity": "sha512-28L5VHlrn9gT9uMeasR5VJkVTc+Xj6eWqZxSQWVsnzznRNJWrHJK5ldK6DMtnWOMTZarPznq8dp0ko4R+svqdg==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/obs-websocket-js/-/obs-websocket-js-5.0.4.tgz", + "integrity": "sha512-6ieuC2rq/mQoEMwO2dVKv3ZqeGL6nKHuSvnd2d/CmXuK2nOfw88NInJUyvpFHNaltmkc6P5ywVZu68Q8K5xvqQ==", "dependencies": { - "debug": "^4.1.0", - "isomorphic-ws": "^4.0.1", - "sha.js": "^2.4.9", - "ws": "^7.2.0" + "@msgpack/msgpack": "^2.7.1", + "crypto-js": "^4.1.1", + "debug": "^4.3.2", + "eventemitter3": "^5.0.1", + "isomorphic-ws": "^5.0.0", + "type-fest": "^3.11.0", + "ws": "^8.13.0" + }, + "engines": { + "node": ">12.0" + } + }, + "node_modules/obs-websocket-js/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/once": { @@ -2183,18 +2218,6 @@ "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2649,15 +2672,15 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/package.json b/package.json index c513243..cc5b679 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "get-video-duration": "3.0.2", "lodash": "^4.17.21", "node-fetch": "^2.7.0", - "obs-websocket-js": "^4.0.3", + "obs-websocket-js": "^5.0.4", "osc": "^2.4.4", "string-similarity": "^4.0.4", "tiny-typed-emitter": "^2.1.0", From 8c6d6692a639df4dfdf4bcd8b6e5049d13725e0d Mon Sep 17 00:00:00 2001 From: duncte123 Date: Sun, 14 Jan 2024 19:42:53 +0100 Subject: [PATCH 02/16] Convert a few items --- extension/obs/src/index.ts | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index c21c15a..251872a 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -37,10 +37,10 @@ class OBS extends EventEmitter { setTimeout(() => this.connect(), 5000); }); - this.conn.on('SwitchScenes', (data) => { + this.conn.on('CurrentProgramSceneChanged', (data) => { const lastScene = this.currentScene; - if (lastScene !== data['scene-name']) { - this.currentScene = data['scene-name']; + if (lastScene !== data.sceneName) { + this.currentScene = data.sceneName; this.emit('currentSceneChanged', this.currentScene, lastScene); } }); @@ -65,42 +65,39 @@ class OBS extends EventEmitter { async connect(): Promise { try { - await this.conn.connect({ - address: this.config.address, - password: this.config.password, - }); + await this.conn.connect(this.config.address, this.config.password); this.connected = true; - const scenes = await this.conn.send('GetSceneList'); + const scenes = await this.conn.call('GetSceneList'); // Get current scene on connection. const lastScene = this.currentScene; - if (lastScene !== scenes['current-scene']) { - this.currentScene = scenes['current-scene']; + if (lastScene !== scenes.currentProgramSceneName) { + this.currentScene = scenes.currentProgramSceneName; } // Get scene list on connection. const oldList = clone(this.sceneList); - const newList = scenes.scenes.map((s) => s.name); + const newList = scenes.scenes.map((s) => s.name as string); if (JSON.stringify(newList) !== JSON.stringify(oldList)) { this.sceneList = newList; } // Get streaming status on connection. - const streamingStatus = await this.conn.send('GetStreamingStatus'); + const streamingStatus = await this.conn.call('GetStreamStatus'); const lastStatus = this.streaming; - if (streamingStatus.streaming !== lastStatus) { - this.streaming = streamingStatus.streaming; + if (streamingStatus.outputActive !== lastStatus) { + this.streaming = streamingStatus.outputActive; } // Emit changes after everything start up related has finished. this.emit('connectionStatusChanged', this.connected); - if (lastScene !== scenes['current-scene']) { + if (lastScene !== scenes.currentProgramSceneName) { this.emit('currentSceneChanged', this.currentScene, lastScene); } if (JSON.stringify(newList) !== JSON.stringify(oldList)) { this.emit('sceneListChanged', this.sceneList); } - if (streamingStatus.streaming !== lastStatus) { + if (streamingStatus.outputActive !== lastStatus) { this.emit('streamingStatusChanged', this.streaming, lastStatus); } From 1aaeb14f14d21b24cbb7992d604cc03e7e2f54f3 Mon Sep 17 00:00:00 2001 From: duncte123 Date: Sun, 14 Jan 2024 19:51:55 +0100 Subject: [PATCH 03/16] Convert some more stuff --- extension/obs/src/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index 251872a..b4c4c0e 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -45,9 +45,8 @@ class OBS extends EventEmitter { } }); - this.conn.on('ScenesChanged', async () => { - const scenes = await this.conn.send('GetSceneList'); - this.sceneList = scenes.scenes.map((s) => s.name); + this.conn.on('SceneListChanged', async ({ scenes }) => { + this.sceneList = scenes.map((s) => s.name as string); this.emit('sceneListChanged', this.sceneList); }); @@ -56,6 +55,7 @@ class OBS extends EventEmitter { this.emit('streamingStatusChanged', this.streaming, !this.streaming); }); + // @ts-expect-error TS2345 error is being fired I think this.conn.on('error', (err) => { nodecg.log.warn('[OBS] Connection error'); nodecg.log.debug('[OBS] Connection error:', err); @@ -145,7 +145,9 @@ class OBS extends EventEmitter { try { const scene = this.findScene(name); if (scene) { - await this.conn.call('SetCurrentScene', { 'scene-name': scene }); + await this.conn.call('SetCurrentProgramScene', { + sceneName: scene, + }); } else { throw new Error('Scene could not be found'); } From 101b2d294f5cd3ceb7871f165e0fd1588772a15b Mon Sep 17 00:00:00 2001 From: duncte123 Date: Sun, 14 Jan 2024 20:23:25 +0100 Subject: [PATCH 04/16] I hope this works --- extension/obs/src/index.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index b4c4c0e..6f916d3 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -163,10 +163,7 @@ class OBS extends EventEmitter { * @param sourceName Name of the source. */ async getSourceSettings(sourceName: string): Promise<{ - messageId: string; - status: 'ok'; - sourceName: string; - sourceType: string; + inputKind: string; sourceSettings: Record; }> { if (!this.config.enabled || !this.connected) { @@ -174,8 +171,13 @@ class OBS extends EventEmitter { throw new Error('No connection available'); } try { - const resp = await this.conn.call('GetSourceSettings', { sourceName }); - return resp; + const resp = await this.conn.call('GetInputSettings', { + inputName: sourceName, + }); + return { + inputKind: resp.inputKind, + sourceSettings: resp.inputSettings, + }; } catch (err) { this.nodecg.log.warn(`[OBS] Cannot get source settings [${sourceName}]`); this.nodecg.log.debug(`[OBS] Cannot get source settings [${sourceName}]: ` @@ -197,10 +199,10 @@ class OBS extends EventEmitter { throw new Error('No connection available'); } try { - await this.conn.call('SetSourceSettings', { - sourceName, - sourceType, - sourceSettings, + await this.conn.call('SetInputSettings', { + inputName: sourceName, + inputSettings: sourceSettings as never, + overlay: true, }); } catch (err) { this.nodecg.log.warn(`[OBS] Cannot set source settings [${sourceName}]`); From 1baf2972d76c99c45adf8458d65e99f733313959 Mon Sep 17 00:00:00 2001 From: duncte123 Date: Mon, 15 Jan 2024 16:20:37 +0100 Subject: [PATCH 05/16] Shared seems to compile --- extension/music/src/index.ts | 7 +- extension/obs/src/index.ts | 134 +++++++++++++++++++++++----- extension/video-player/src/index.ts | 40 +++++---- 3 files changed, 138 insertions(+), 43 deletions(-) diff --git a/extension/music/src/index.ts b/extension/music/src/index.ts index b1b5be2..44a7270 100644 --- a/extension/music/src/index.ts +++ b/extension/music/src/index.ts @@ -168,9 +168,10 @@ class Music { }); // Listen to OBS transitions to play/pause correctly. - this.obs.conn.on('TransitionBegin', (data) => { - if (data['to-scene']) { - if (data['to-scene'].includes('[M]')) { + // In v5, transitions don't tell us about the scene that is being transitioned to. + this.obs.conn.on('CurrentProgramSceneChanged', (data) => { + if (data.sceneName) { + if (data.sceneName.includes('[M]')) { this.play(); } else { this.pause(); diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index 6f916d3..1c7f019 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -189,11 +189,10 @@ class OBS extends EventEmitter { /** * Modify a sources settings. * @param sourceName Name of the source. - * @param sourceType Source type (has the be the internal name, not the display name). * @param sourceSettings Settings you wish to pass to OBS to change. */ // eslint-disable-next-line max-len - async setSourceSettings(sourceName: string, sourceType: string, sourceSettings: Record): Promise { + async setSourceSettings(sourceName: string, sourceSettings: Record): Promise { if (!this.config.enabled || !this.connected) { // OBS not enabled, don't even try to set. throw new Error('No connection available'); @@ -202,7 +201,6 @@ class OBS extends EventEmitter { await this.conn.call('SetInputSettings', { inputName: sourceName, inputSettings: sourceSettings as never, - overlay: true, }); } catch (err) { this.nodecg.log.warn(`[OBS] Cannot set source settings [${sourceName}]`); @@ -212,6 +210,73 @@ class OBS extends EventEmitter { } } + /** + * @deprecated for removal, use getSceneItemSettings instead + */ + async getItemTransform(scene: string, item: string) + : Promise<{ sceneItemTransform: Record, visible: boolean }> { + const { sceneItemId } = await this.conn.call('GetSceneItemId', { + sceneName: scene, + sourceName: item, + }); + + const { sceneItemEnabled } = await this.conn.call('GetSceneItemEnabled', { + sceneName: scene, + sceneItemId, + }); + + const { sceneItemTransform } = await this.conn.call('GetSceneItemTransform', { + sceneName: scene, + sceneItemId, + }); + + return { + sceneItemTransform, + visible: sceneItemEnabled, + }; + } + + async getSceneItemSettings( + scene: string, + item: string, + ) { + const test = await this.conn.callBatch([ + { + requestType: 'GetSceneItemId', + requestData: { + sceneName: scene, + sourceName: item, + }, + // @ts-expect-error this is just dumb + outputVariables: { + sceneItemIdVariable: 'sceneItemId', + }, + }, + { + requestType: 'GetSceneItemTransform', + // @ts-expect-error the sceneItemId var is optional cuz of the input vars + requestData: { + sceneName: scene, + }, + inputVariables: { + sceneItemId: 'sceneItemIdVariable', + }, + }, + { + requestType: 'GetSceneItemEnabled', + // @ts-expect-error the sceneItemId var is optional cuz of the input vars + requestData: { + sceneName: scene, + }, + inputVariables: { + sceneItemId: 'sceneItemIdVariable', + }, + }, + ]); + + console.log(test); + } + /** * Resets the scene item, then sets some properties if possible. * @param scene Name of scene that item is in @@ -242,28 +307,51 @@ class OBS extends EventEmitter { // OBS not enabled, don't even try to set. throw new Error('No connection available'); } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore: Typings say we need to specify more than we actually do. - await this.conn.call('SetSceneItemProperties', { - 'scene-name': scene, - item: { name: item }, - visible: visible ?? true, - position: { - x: area?.x ?? 0, - y: area?.y ?? 0, - }, - bounds: { - type: 'OBS_BOUNDS_STRETCH', - x: area?.width ?? 1920, - y: area?.height ?? 1080, + + // None of the objects are properly documented btw, like wtf guys + const test = await this.conn.callBatch([ + { + requestType: 'GetSceneItemId', + requestData: { + sceneName: scene, + sourceName: item, + }, + // @ts-expect-error this is just dumb + outputVariables: { + sceneItemIdVariable: 'sceneItemId', + }, }, - crop: { - top: crop?.top ?? 0, - bottom: crop?.bottom ?? 0, - left: crop?.left ?? 0, - right: crop?.right ?? 0, + { + requestType: 'SetSceneItemTransform', + // @ts-expect-error the sceneItemId var is optional cuz of the input vars + requestData: { + sceneName: scene, + sceneItemTransform: { + visible: visible ?? true, + position: { + x: area?.x ?? 0, + y: area?.y ?? 0, + }, + bounds: { + type: 'OBS_BOUNDS_STRETCH', + x: area?.width ?? 1920, + y: area?.height ?? 1080, + }, + crop: { + top: crop?.top ?? 0, + bottom: crop?.bottom ?? 0, + left: crop?.left ?? 0, + right: crop?.right ?? 0, + }, + }, + }, + inputVariables: { + sceneItemId: 'sceneItemIdVariable', + }, }, - }); + ]); + + console.log(test); } catch (err) { this.nodecg.log.warn(`[OBS] Cannot configure scene item [${scene}: ${item}]`); this.nodecg.log.debug(`[OBS] Cannot configure scene item [${scene}: ${item}]: ` diff --git a/extension/video-player/src/index.ts b/extension/video-player/src/index.ts index 92b4d0d..fb263a9 100644 --- a/extension/video-player/src/index.ts +++ b/extension/video-player/src/index.ts @@ -30,8 +30,8 @@ class VideoPlayer extends TypedEmitter { this.obs = obs; // Listens for when videos finish playing in OBS. - obs.conn.on('MediaEnded', (data) => { - if (data.sourceName === this.obsConfig.names.sources.videoPlayer + obs.conn.on('MediaInputPlaybackEnded', (data) => { + if (data.inputName === this.obsConfig.names.sources.videoPlayer && this.playing && this.index >= 0) { this.emit('videoEnded', this.playlist[this.index]); } @@ -110,9 +110,15 @@ class VideoPlayer extends TypedEmitter { this.playlist.length = 0; this.delayAC?.abort(); try { - await this.obs.conn.send( - 'StopMedia', - { sourceName: this.obsConfig.names.sources.videoPlayer }, + await this.obs.conn.call( + 'TriggerMediaInputAction', + { + inputName: this.obsConfig.names.sources.videoPlayer, + // Sooo, fun fact. + // "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP" is deprecated. + // This is also the only way to stop media currently. + mediaAction: 'OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP', + }, ); } catch (err) { /* do nothing */ } this.emit('playlistEnded', true); @@ -127,24 +133,24 @@ class VideoPlayer extends TypedEmitter { if (!this.obs.connected || !this.obsConfig.enabled) { throw new Error('no OBS connection available'); } - const source = await this.obs.conn.send('GetSourceSettings', { - sourceName: this.obsConfig.names.sources.videoPlayer, + const source = await this.obs.conn.call('GetInputSettings', { + inputName: this.obsConfig.names.sources.videoPlayer, }); const location = join(cwd(), `assets/${video.namespace}/${video.category}/${video.base}`); - if (source.sourceType === 'ffmpeg_source') { - await this.obs.conn.send('SetSourceSettings', { - sourceName: this.obsConfig.names.sources.videoPlayer, - sourceSettings: { + if (source.inputKind === 'ffmpeg_source') { + await this.obs.setSourceSettings( + this.obsConfig.names.sources.videoPlayer, + { is_local_file: true, local_file: location, looping: false, restart_on_activate: false, }, - }); - } else if (source.sourceType === 'vlc_source') { - await this.obs.conn.send('SetSourceSettings', { - sourceName: this.obsConfig.names.sources.videoPlayer, - sourceSettings: { + ); + } else if (source.inputKind === 'vlc_source') { + await this.obs.setSourceSettings( + this.obsConfig.names.sources.videoPlayer, + { loop: false, shuffle: false, playback_behavior: 'always_play', @@ -156,7 +162,7 @@ class VideoPlayer extends TypedEmitter { }, ], }, - }); + ); } else { throw new Error('No video player source found in OBS to trigger'); } From 649de47ce9e68f443d3a33b13eb10bfcdd4986e9 Mon Sep 17 00:00:00 2001 From: duncte123 Date: Mon, 15 Jan 2024 16:44:37 +0100 Subject: [PATCH 06/16] Store obs crop into interface --- extension/obs/src/index.ts | 77 +++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index 1c7f019..828d2bd 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -3,8 +3,29 @@ import clone from 'clone'; import { EventEmitter } from 'events'; import OBSWebSocket from 'obs-websocket-js'; import { findBestMatch } from 'string-similarity'; +import { OBSResponseTypes } from 'obs-websocket-js/dist/types'; import { OBS as OBSTypes } from '../../../types'; +export interface OBSTransform { + alignment: number; + boundsAlignment: number; + boundsHeight: number; + boundsType: string; + boundsWidth: number; + cropBottom: number; + cropLeft: number; + cropRight: number; + cropTop: number; + positionX: number; + positionY: number; + rotation: number; + scaleX: number; + scaleY: number; + sourceHeight: number; + sourceWidth: number; + width: number; +} + interface OBS { on(event: 'streamingStatusChanged', listener: (streaming: boolean, old?: boolean) => void): this; on(event: 'connectionStatusChanged', listener: (connected: boolean) => void): this; @@ -214,7 +235,7 @@ class OBS extends EventEmitter { * @deprecated for removal, use getSceneItemSettings instead */ async getItemTransform(scene: string, item: string) - : Promise<{ sceneItemTransform: Record, visible: boolean }> { + : Promise<{ sceneItemTransform: OBSTransform, visible: boolean }> { const { sceneItemId } = await this.conn.call('GetSceneItemId', { sceneName: scene, sourceName: item, @@ -231,7 +252,7 @@ class OBS extends EventEmitter { }); return { - sceneItemTransform, + sceneItemTransform: sceneItemTransform as unknown as OBSTransform, visible: sceneItemEnabled, }; } @@ -239,8 +260,8 @@ class OBS extends EventEmitter { async getSceneItemSettings( scene: string, item: string, - ) { - const test = await this.conn.callBatch([ + ): Promise<{ sceneItemTransform: OBSTransform, sceneItemEnabled: boolean }> { + const response = await this.conn.callBatch([ { requestType: 'GetSceneItemId', requestData: { @@ -274,7 +295,13 @@ class OBS extends EventEmitter { }, ]); - console.log(test); + const transformRes = response[1].responseData as OBSResponseTypes['GetSceneItemTransform']; + const enabledRes = response[2].responseData as OBSResponseTypes['GetSceneItemEnabled']; + + return { + sceneItemTransform: transformRes.sceneItemTransform as unknown as OBSTransform, + sceneItemEnabled: enabledRes.sceneItemEnabled, + }; } /** @@ -327,31 +354,37 @@ class OBS extends EventEmitter { requestData: { sceneName: scene, sceneItemTransform: { - visible: visible ?? true, - position: { - x: area?.x ?? 0, - y: area?.y ?? 0, - }, - bounds: { - type: 'OBS_BOUNDS_STRETCH', - x: area?.width ?? 1920, - y: area?.height ?? 1080, - }, - crop: { - top: crop?.top ?? 0, - bottom: crop?.bottom ?? 0, - left: crop?.left ?? 0, - right: crop?.right ?? 0, - }, + boundsHeight: area?.height ?? 1080, + boundsType: 'OBS_BOUNDS_STRETCH', + boundsWidth: area?.width ?? 1920, + + positionX: area?.x ?? 0, + positionY: area?.y ?? 0, + + cropBottom: crop?.bottom ?? 0, + cropLeft: crop?.left ?? 0, + cropRight: crop?.right ?? 0, + cropTop: crop?.top ?? 0, }, }, inputVariables: { sceneItemId: 'sceneItemIdVariable', }, }, + { + requestType: 'SetSceneItemEnabled', + // @ts-expect-error the sceneItemId var is optional cuz of the input vars + requestData: { + sceneName: scene, + sceneItemEnabled: visible ?? true, + }, + inputVariables: { + sceneItemId: 'sceneItemIdVariable', + }, + }, ]); - console.log(test); + console.log(JSON.stringify(test, null, 2)); } catch (err) { this.nodecg.log.warn(`[OBS] Cannot configure scene item [${scene}: ${item}]`); this.nodecg.log.debug(`[OBS] Cannot configure scene item [${scene}: ${item}]: ` From fcf089398ed0a042550e5e5f6c079154ad0946ee Mon Sep 17 00:00:00 2001 From: duncte123 Date: Mon, 15 Jan 2024 19:42:20 +0100 Subject: [PATCH 07/16] It runs --- extension/music/src/index.ts | 7 ++++--- extension/obs/src/index.ts | 31 +++++++++++++++++++++---------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/extension/music/src/index.ts b/extension/music/src/index.ts index 44a7270..a6d997d 100644 --- a/extension/music/src/index.ts +++ b/extension/music/src/index.ts @@ -169,9 +169,10 @@ class Music { // Listen to OBS transitions to play/pause correctly. // In v5, transitions don't tell us about the scene that is being transitioned to. - this.obs.conn.on('CurrentProgramSceneChanged', (data) => { - if (data.sceneName) { - if (data.sceneName.includes('[M]')) { + // this is why we look at when the current scene is changed + this.obs.on('currentSceneChanged', (current) => { + if (current) { + if (current.includes('[M]')) { this.play(); } else { this.pause(); diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index 828d2bd..0856931 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -31,6 +31,7 @@ interface OBS { on(event: 'connectionStatusChanged', listener: (connected: boolean) => void): this; on(event: 'currentSceneChanged', listener: (current?: string, last?: string) => void): this; on(event: 'sceneListChanged', listener: (list: string[]) => void): this; + on(event: 'ready', listener: () => void): this; } class OBS extends EventEmitter { @@ -49,7 +50,6 @@ class OBS extends EventEmitter { if (config.enabled) { nodecg.log.info('[OBS] Setting up connection'); - this.connect(); this.conn.on('ConnectionClosed', () => { this.connected = false; @@ -67,7 +67,9 @@ class OBS extends EventEmitter { }); this.conn.on('SceneListChanged', async ({ scenes }) => { - this.sceneList = scenes.map((s) => s.name as string); + this.sceneList = (scenes as { sceneIndex: number, sceneName: string }[]) + .sort((s, b) => b.sceneIndex - s.sceneIndex) + .map((s) => s.sceneName); this.emit('sceneListChanged', this.sceneList); }); @@ -76,11 +78,20 @@ class OBS extends EventEmitter { this.emit('streamingStatusChanged', this.streaming, !this.streaming); }); - // @ts-expect-error TS2345 error is being fired I think - this.conn.on('error', (err) => { + this.conn.on('ConnectionError', (err) => { nodecg.log.warn('[OBS] Connection error'); nodecg.log.debug('[OBS] Connection error:', err); }); + + this.conn.on('Identified', () => { + // wait a few seconds to make sure stuff is loaded. + // Otherwise, we'll get "OBS is not ready to perform the request" + setTimeout(() => { + this.emit('ready'); + }, 5 * 1000); + }); + + this.connect(); } } @@ -98,7 +109,9 @@ class OBS extends EventEmitter { // Get scene list on connection. const oldList = clone(this.sceneList); - const newList = scenes.scenes.map((s) => s.name as string); + const newList = (scenes.scenes as { sceneIndex: number, sceneName: string }[]) + .sort((s, b) => b.sceneIndex - s.sceneIndex) + .map((s) => s.sceneName); if (JSON.stringify(newList) !== JSON.stringify(oldList)) { this.sceneList = newList; } @@ -232,7 +245,7 @@ class OBS extends EventEmitter { } /** - * @deprecated for removal, use getSceneItemSettings instead + * @deprecated for removal, use getSceneItemSettings instead, it's also faster. */ async getItemTransform(scene: string, item: string) : Promise<{ sceneItemTransform: OBSTransform, visible: boolean }> { @@ -336,14 +349,14 @@ class OBS extends EventEmitter { } // None of the objects are properly documented btw, like wtf guys - const test = await this.conn.callBatch([ + await this.conn.callBatch([ { requestType: 'GetSceneItemId', requestData: { sceneName: scene, sourceName: item, }, - // @ts-expect-error this is just dumb + // @ts-expect-error This is valid, just undocumented and not typed in obs-ws-js. outputVariables: { sceneItemIdVariable: 'sceneItemId', }, @@ -383,8 +396,6 @@ class OBS extends EventEmitter { }, }, ]); - - console.log(JSON.stringify(test, null, 2)); } catch (err) { this.nodecg.log.warn(`[OBS] Cannot configure scene item [${scene}: ${item}]`); this.nodecg.log.debug(`[OBS] Cannot configure scene item [${scene}: ${item}]: ` From 46a52a9ba689124b3aa9e96a4016d484d15daa80 Mon Sep 17 00:00:00 2001 From: duncte123 Date: Mon, 15 Jan 2024 19:59:15 +0100 Subject: [PATCH 08/16] Format code a bit nicer --- extension/obs/src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index 0856931..e987cbb 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -281,7 +281,7 @@ class OBS extends EventEmitter { sceneName: scene, sourceName: item, }, - // @ts-expect-error this is just dumb + // @ts-expect-error This is valid, just undocumented and not typed in obs-ws-js. outputVariables: { sceneItemIdVariable: 'sceneItemId', }, @@ -348,7 +348,8 @@ class OBS extends EventEmitter { throw new Error('No connection available'); } - // None of the objects are properly documented btw, like wtf guys + // None of this is properly documented btw. + // I had to search their discord for this information. await this.conn.callBatch([ { requestType: 'GetSceneItemId', From 19ba3a26abeeb955d9060f65292e0701f8b1aa2e Mon Sep 17 00:00:00 2001 From: zoton2 Date: Mon, 15 Jan 2024 21:11:51 +0000 Subject: [PATCH 09/16] Tweaking some comments --- extension/music/src/index.ts | 3 ++- extension/video-player/src/index.ts | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/extension/music/src/index.ts b/extension/music/src/index.ts index a6d997d..cff954c 100644 --- a/extension/music/src/index.ts +++ b/extension/music/src/index.ts @@ -168,8 +168,9 @@ class Music { }); // Listen to OBS transitions to play/pause correctly. + // In v4, we were listening to "TransitionBegin" but not sure why. // In v5, transitions don't tell us about the scene that is being transitioned to. - // this is why we look at when the current scene is changed + // This is why we look at when the current scene is changed now. this.obs.on('currentSceneChanged', (current) => { if (current) { if (current.includes('[M]')) { diff --git a/extension/video-player/src/index.ts b/extension/video-player/src/index.ts index fb263a9..bf20888 100644 --- a/extension/video-player/src/index.ts +++ b/extension/video-player/src/index.ts @@ -114,9 +114,7 @@ class VideoPlayer extends TypedEmitter { 'TriggerMediaInputAction', { inputName: this.obsConfig.names.sources.videoPlayer, - // Sooo, fun fact. - // "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP" is deprecated. - // This is also the only way to stop media currently. + // This action is incorrectly marked as deprecated in the generated docs, but isn't. mediaAction: 'OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP', }, ); From 36df14099fe91e58d9362c5b3927c59ad36c54b8 Mon Sep 17 00:00:00 2001 From: zoton2 Date: Mon, 15 Jan 2024 21:13:09 +0000 Subject: [PATCH 10/16] extension\obs: removing deprecated function --- extension/obs/src/index.ts | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index e987cbb..ae59b3f 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -2,8 +2,8 @@ import type NodeCGTypes from '@nodecg/types'; import clone from 'clone'; import { EventEmitter } from 'events'; import OBSWebSocket from 'obs-websocket-js'; -import { findBestMatch } from 'string-similarity'; import { OBSResponseTypes } from 'obs-websocket-js/dist/types'; +import { findBestMatch } from 'string-similarity'; import { OBS as OBSTypes } from '../../../types'; export interface OBSTransform { @@ -244,32 +244,6 @@ class OBS extends EventEmitter { } } - /** - * @deprecated for removal, use getSceneItemSettings instead, it's also faster. - */ - async getItemTransform(scene: string, item: string) - : Promise<{ sceneItemTransform: OBSTransform, visible: boolean }> { - const { sceneItemId } = await this.conn.call('GetSceneItemId', { - sceneName: scene, - sourceName: item, - }); - - const { sceneItemEnabled } = await this.conn.call('GetSceneItemEnabled', { - sceneName: scene, - sceneItemId, - }); - - const { sceneItemTransform } = await this.conn.call('GetSceneItemTransform', { - sceneName: scene, - sceneItemId, - }); - - return { - sceneItemTransform: sceneItemTransform as unknown as OBSTransform, - visible: sceneItemEnabled, - }; - } - async getSceneItemSettings( scene: string, item: string, From 7159b82cd38572ad5d53fd444d28315e0acb59e1 Mon Sep 17 00:00:00 2001 From: duncte123 Date: Tue, 16 Jan 2024 09:56:24 +0100 Subject: [PATCH 11/16] Comments from zoton --- extension/obs/src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index ae59b3f..42c641f 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -84,7 +84,7 @@ class OBS extends EventEmitter { }); this.conn.on('Identified', () => { - // wait a few seconds to make sure stuff is loaded. + // wait a few seconds to make sure OBS is properly loaded. // Otherwise, we'll get "OBS is not ready to perform the request" setTimeout(() => { this.emit('ready'); @@ -248,6 +248,8 @@ class OBS extends EventEmitter { scene: string, item: string, ): Promise<{ sceneItemTransform: OBSTransform, sceneItemEnabled: boolean }> { + // None of this is properly documented btw. + // I had to search their discord for this information. const response = await this.conn.callBatch([ { requestType: 'GetSceneItemId', From c0249ab51b9b75af532c0609b3f59c9dd8023403 Mon Sep 17 00:00:00 2001 From: duncte123 Date: Tue, 16 Jan 2024 20:19:13 +0100 Subject: [PATCH 12/16] Emit custom transition event for mixer and music --- extension/music/src/index.ts | 2 +- extension/obs/src/index.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/extension/music/src/index.ts b/extension/music/src/index.ts index cff954c..ae94919 100644 --- a/extension/music/src/index.ts +++ b/extension/music/src/index.ts @@ -171,7 +171,7 @@ class Music { // In v4, we were listening to "TransitionBegin" but not sure why. // In v5, transitions don't tell us about the scene that is being transitioned to. // This is why we look at when the current scene is changed now. - this.obs.on('currentSceneChanged', (current) => { + this.obs.on('transitionStarted', (current) => { if (current) { if (current.includes('[M]')) { this.play(); diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index 42c641f..f6145ad 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -30,6 +30,7 @@ interface OBS { on(event: 'streamingStatusChanged', listener: (streaming: boolean, old?: boolean) => void): this; on(event: 'connectionStatusChanged', listener: (connected: boolean) => void): this; on(event: 'currentSceneChanged', listener: (current?: string, last?: string) => void): this; + on(event: 'transitionStarted', listener: (current: string, previous?: string) => void): this; on(event: 'sceneListChanged', listener: (list: string[]) => void): this; on(event: 'ready', listener: () => void): this; } @@ -182,6 +183,7 @@ class OBS extends EventEmitter { await this.conn.call('SetCurrentProgramScene', { sceneName: scene, }); + this.emit('transitionStarted', scene, this.currentScene); } else { throw new Error('Scene could not be found'); } From 3e5eaddb36a375552db3e7d894b0060fd53facc2 Mon Sep 17 00:00:00 2001 From: duncte123 Date: Wed, 17 Jan 2024 08:07:52 +0100 Subject: [PATCH 13/16] Move types to types package --- extension/obs/src/index.ts | 28 ++++------------------------ types/OBS.d.ts | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index f6145ad..4aad0a9 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -6,26 +6,6 @@ import { OBSResponseTypes } from 'obs-websocket-js/dist/types'; import { findBestMatch } from 'string-similarity'; import { OBS as OBSTypes } from '../../../types'; -export interface OBSTransform { - alignment: number; - boundsAlignment: number; - boundsHeight: number; - boundsType: string; - boundsWidth: number; - cropBottom: number; - cropLeft: number; - cropRight: number; - cropTop: number; - positionX: number; - positionY: number; - rotation: number; - scaleX: number; - scaleY: number; - sourceHeight: number; - sourceWidth: number; - width: number; -} - interface OBS { on(event: 'streamingStatusChanged', listener: (streaming: boolean, old?: boolean) => void): this; on(event: 'connectionStatusChanged', listener: (connected: boolean) => void): this; @@ -68,7 +48,7 @@ class OBS extends EventEmitter { }); this.conn.on('SceneListChanged', async ({ scenes }) => { - this.sceneList = (scenes as { sceneIndex: number, sceneName: string }[]) + this.sceneList = (scenes as OBSTypes.SceneList) .sort((s, b) => b.sceneIndex - s.sceneIndex) .map((s) => s.sceneName); this.emit('sceneListChanged', this.sceneList); @@ -110,7 +90,7 @@ class OBS extends EventEmitter { // Get scene list on connection. const oldList = clone(this.sceneList); - const newList = (scenes.scenes as { sceneIndex: number, sceneName: string }[]) + const newList = (scenes.scenes as OBSTypes.SceneList) .sort((s, b) => b.sceneIndex - s.sceneIndex) .map((s) => s.sceneName); if (JSON.stringify(newList) !== JSON.stringify(oldList)) { @@ -249,7 +229,7 @@ class OBS extends EventEmitter { async getSceneItemSettings( scene: string, item: string, - ): Promise<{ sceneItemTransform: OBSTransform, sceneItemEnabled: boolean }> { + ): Promise<{ sceneItemTransform: OBSTypes.Transform, sceneItemEnabled: boolean }> { // None of this is properly documented btw. // I had to search their discord for this information. const response = await this.conn.callBatch([ @@ -290,7 +270,7 @@ class OBS extends EventEmitter { const enabledRes = response[2].responseData as OBSResponseTypes['GetSceneItemEnabled']; return { - sceneItemTransform: transformRes.sceneItemTransform as unknown as OBSTransform, + sceneItemTransform: transformRes.sceneItemTransform as unknown as OBSTypes.Transform, sceneItemEnabled: enabledRes.sceneItemEnabled, }; } diff --git a/types/OBS.d.ts b/types/OBS.d.ts index c2cf77d..d7462db 100644 --- a/types/OBS.d.ts +++ b/types/OBS.d.ts @@ -13,4 +13,29 @@ export namespace OBS { }; }; } + + interface Transform { + alignment: number; + boundsAlignment: number; + boundsHeight: number; + boundsType: string; + boundsWidth: number; + cropBottom: number; + cropLeft: number; + cropRight: number; + cropTop: number; + positionX: number; + positionY: number; + rotation: number; + scaleX: number; + scaleY: number; + sourceHeight: number; + sourceWidth: number; + width: number; + } + + type SceneList = [{ + sceneIndex: number; + sceneName: string; + }]; } From 3e85b27d5b306e7a0dd225be1c025dc7d38194a5 Mon Sep 17 00:00:00 2001 From: duncte123 Date: Wed, 5 Jun 2024 18:09:16 +0200 Subject: [PATCH 14/16] OBS websocket v5 works! With a custom plugin --- extension/obs/src/index.ts | 22 ++++++++- extension/video-player/src/index.ts | 9 ++-- pnpm-lock.yaml | 73 +++++++++++++++-------------- 3 files changed, 63 insertions(+), 41 deletions(-) diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index 4aad0a9..24f6dde 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -15,6 +15,13 @@ interface OBS { on(event: 'ready', listener: () => void): this; } +type SexyTransitionData = { + fromScene: string; + toScene: string; + transitionName: string; + transitionUuid: string; +}; + class OBS extends EventEmitter { private nodecg: NodeCGTypes.ServerAPI; private config: OBSTypes.Config; @@ -64,6 +71,11 @@ class OBS extends EventEmitter { nodecg.log.debug('[OBS] Connection error:', err); }); + // @ts-expect-error better types + this.conn.on('SceneTransitionStarted', (data: SexyTransitionData) => { + this.emit('transitionStarted', data.toScene, data.fromScene); + }); + this.conn.on('Identified', () => { // wait a few seconds to make sure OBS is properly loaded. // Otherwise, we'll get "OBS is not ready to perform the request" @@ -78,7 +90,13 @@ class OBS extends EventEmitter { async connect(): Promise { try { - await this.conn.connect(this.config.address, this.config.password); + let addr = this.config.address; + + if (!addr.startsWith('ws://')) { + addr = `ws://${addr}`; + } + + await this.conn.connect(addr, this.config.password); this.connected = true; const scenes = await this.conn.call('GetSceneList'); @@ -163,7 +181,7 @@ class OBS extends EventEmitter { await this.conn.call('SetCurrentProgramScene', { sceneName: scene, }); - this.emit('transitionStarted', scene, this.currentScene); + // this.emit('transitionStarted', scene, this.currentScene); } else { throw new Error('Scene could not be found'); } diff --git a/extension/video-player/src/index.ts b/extension/video-player/src/index.ts index de95b56..0f1def5 100644 --- a/extension/video-player/src/index.ts +++ b/extension/video-player/src/index.ts @@ -138,10 +138,11 @@ class VideoPlayer extends TypedEmitter { // File is the same as before, just restart it. // ONLY WORKS FOR ffmpeg_source, but might not even be needed for VLC source! // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((source.sourceSettings as any).local_file === location) { - await this.obs.conn.send('RestartMedia', { - sourceName: this.obsConfig.names.sources.videoPlayer, - }); + if ((source.inputSettings as any).local_file === location) { + // TODO: AAAAAAAAAAAAAAAAAAAAAAA + // await this.obs.conn.call('RestartMedia', { + // sourceName: this.obsConfig.names.sources.videoPlayer, + // }); // If different, explicitily set it. This also starts the playback. } else if (source.inputKind === 'ffmpeg_source') { await this.obs.setSourceSettings( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 480ee2f..08701a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,8 +30,8 @@ importers: specifier: ^2.7.0 version: 2.7.0 obs-websocket-js: - specifier: ^4.0.3 - version: 4.0.3 + specifier: ^5.0.4 + version: 5.0.5 osc: specifier: ^2.4.4 version: 2.4.4 @@ -149,6 +149,10 @@ packages: '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@msgpack/msgpack@2.8.0': + resolution: {integrity: sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==} + engines: {node: '>= 10'} + '@serialport/binding-mock@10.2.2': resolution: {integrity: sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==} engines: {node: '>=12.0.0'} @@ -383,6 +387,9 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + d@1.0.2: resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} engines: {node: '>=0.12'} @@ -446,6 +453,9 @@ packages: event-emitter@0.3.5: resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + execa@4.1.0: resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} engines: {node: '>=10'} @@ -572,8 +582,8 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isomorphic-ws@4.0.1: - resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} + isomorphic-ws@5.0.0: + resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} peerDependencies: ws: '*' @@ -721,8 +731,9 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - obs-websocket-js@4.0.3: - resolution: {integrity: sha512-28L5VHlrn9gT9uMeasR5VJkVTc+Xj6eWqZxSQWVsnzznRNJWrHJK5ldK6DMtnWOMTZarPznq8dp0ko4R+svqdg==} + obs-websocket-js@5.0.5: + resolution: {integrity: sha512-mSMqLXJ4z28jgwy7Ecv8CtpYh/xdbcn524kq0n6wT3kN6xkgWU/Zc6OtiVZo+gyyylC0anjehMLEVF+CDSwccw==} + engines: {node: '>12.0'} once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -825,10 +836,6 @@ packages: resolution: {integrity: sha512-7OYLDsu5i6bbv3lU81pGy076xe0JwpK6b49G6RjNvGibstUqQkI+I3/X491yBGtf4gaqUdOgoU1/5KZ/XxL4dw==} engines: {node: '>=12.0.0'} - sha.js@2.4.11: - resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} - hasBin: true - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -932,6 +939,10 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} + type-fest@3.13.1: + resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} + engines: {node: '>=14.16'} + type@2.7.2: resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} @@ -986,18 +997,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@7.5.9: - resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.13.0: resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} engines: {node: '>=10.0.0'} @@ -1073,6 +1072,8 @@ snapshots: '@jsdevtools/ono@7.1.3': {} + '@msgpack/msgpack@2.8.0': {} + '@serialport/binding-mock@10.2.2': dependencies: '@serialport/bindings-interface': 1.2.2 @@ -1315,6 +1316,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crypto-js@4.2.0: {} + d@1.0.2: dependencies: es5-ext: 0.10.64 @@ -1381,6 +1384,8 @@ snapshots: d: 1.0.2 es5-ext: 0.10.64 + eventemitter3@5.0.1: {} + execa@4.1.0: dependencies: cross-spawn: 7.0.3 @@ -1520,9 +1525,9 @@ snapshots: isexe@2.0.0: {} - isomorphic-ws@4.0.1(ws@7.5.9): + isomorphic-ws@5.0.0(ws@8.13.0): dependencies: - ws: 7.5.9 + ws: 8.13.0 js-yaml@4.1.0: dependencies: @@ -1682,12 +1687,15 @@ snapshots: object-assign@4.1.1: {} - obs-websocket-js@4.0.3: + obs-websocket-js@5.0.5: dependencies: + '@msgpack/msgpack': 2.8.0 + crypto-js: 4.2.0 debug: 4.3.4 - isomorphic-ws: 4.0.1(ws@7.5.9) - sha.js: 2.4.11 - ws: 7.5.9 + eventemitter3: 5.0.1 + isomorphic-ws: 5.0.0(ws@8.13.0) + type-fest: 3.13.1 + ws: 8.13.0 transitivePeerDependencies: - bufferutil - supports-color @@ -1820,11 +1828,6 @@ snapshots: - supports-color optional: true - sha.js@2.4.11: - dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -1930,6 +1933,8 @@ snapshots: type-fest@0.21.3: {} + type-fest@3.13.1: {} + type@2.7.2: {} undici-types@5.26.5: {} @@ -1979,8 +1984,6 @@ snapshots: wrappy@1.0.2: {} - ws@7.5.9: {} - ws@8.13.0: {} xkeys@2.4.0: From 56f3eaabea5eda8cbcb59b72f4bb3d8a5bf5733d Mon Sep 17 00:00:00 2001 From: duncte123 Date: Wed, 5 Jun 2024 19:04:51 +0200 Subject: [PATCH 15/16] Fix restart action and clean some code --- extension/music/src/index.ts | 3 --- extension/obs/src/index.ts | 3 +-- extension/video-player/src/index.ts | 9 +++++---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/extension/music/src/index.ts b/extension/music/src/index.ts index ae94919..2e1e290 100644 --- a/extension/music/src/index.ts +++ b/extension/music/src/index.ts @@ -168,9 +168,6 @@ class Music { }); // Listen to OBS transitions to play/pause correctly. - // In v4, we were listening to "TransitionBegin" but not sure why. - // In v5, transitions don't tell us about the scene that is being transitioned to. - // This is why we look at when the current scene is changed now. this.obs.on('transitionStarted', (current) => { if (current) { if (current.includes('[M]')) { diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index 24f6dde..45d57f1 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -71,7 +71,7 @@ class OBS extends EventEmitter { nodecg.log.debug('[OBS] Connection error:', err); }); - // @ts-expect-error better types + // @ts-expect-error better types are needed. this.conn.on('SceneTransitionStarted', (data: SexyTransitionData) => { this.emit('transitionStarted', data.toScene, data.fromScene); }); @@ -181,7 +181,6 @@ class OBS extends EventEmitter { await this.conn.call('SetCurrentProgramScene', { sceneName: scene, }); - // this.emit('transitionStarted', scene, this.currentScene); } else { throw new Error('Scene could not be found'); } diff --git a/extension/video-player/src/index.ts b/extension/video-player/src/index.ts index 0f1def5..db99814 100644 --- a/extension/video-player/src/index.ts +++ b/extension/video-player/src/index.ts @@ -139,10 +139,11 @@ class VideoPlayer extends TypedEmitter { // ONLY WORKS FOR ffmpeg_source, but might not even be needed for VLC source! // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((source.inputSettings as any).local_file === location) { - // TODO: AAAAAAAAAAAAAAAAAAAAAAA - // await this.obs.conn.call('RestartMedia', { - // sourceName: this.obsConfig.names.sources.videoPlayer, - // }); + await this.obs.conn.call('TriggerMediaInputAction', { + inputName: this.obsConfig.names.sources.videoPlayer, + // This action is incorrectly marked as deprecated in the generated docs, but isn't. + mediaAction: 'OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART', + }); // If different, explicitily set it. This also starts the playback. } else if (source.inputKind === 'ffmpeg_source') { await this.obs.setSourceSettings( From 263b59c56aadc4fd63bb5363cb04112045e0d16d Mon Sep 17 00:00:00 2001 From: duncte123 Date: Wed, 5 Jun 2024 19:06:07 +0200 Subject: [PATCH 16/16] Move OBS types to types --- extension/obs/src/index.ts | 9 +-------- types/OBS.d.ts | 7 +++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/extension/obs/src/index.ts b/extension/obs/src/index.ts index 45d57f1..0f4179f 100644 --- a/extension/obs/src/index.ts +++ b/extension/obs/src/index.ts @@ -15,13 +15,6 @@ interface OBS { on(event: 'ready', listener: () => void): this; } -type SexyTransitionData = { - fromScene: string; - toScene: string; - transitionName: string; - transitionUuid: string; -}; - class OBS extends EventEmitter { private nodecg: NodeCGTypes.ServerAPI; private config: OBSTypes.Config; @@ -72,7 +65,7 @@ class OBS extends EventEmitter { }); // @ts-expect-error better types are needed. - this.conn.on('SceneTransitionStarted', (data: SexyTransitionData) => { + this.conn.on('SceneTransitionStarted', (data: OBSTypes.SexyTransitionData) => { this.emit('transitionStarted', data.toScene, data.fromScene); }); diff --git a/types/OBS.d.ts b/types/OBS.d.ts index d7462db..a4a0de1 100644 --- a/types/OBS.d.ts +++ b/types/OBS.d.ts @@ -14,6 +14,13 @@ export namespace OBS { }; } + interface SexyTransitionData { + fromScene: string; + toScene: string; + transitionName: string; + transitionUuid: string; + } + interface Transform { alignment: number; boundsAlignment: number;