From 347e5348b43cdd5e771d47555035e3fac03212fc Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Sun, 19 Jul 2020 18:20:24 +0200 Subject: [PATCH] Integrate WMS music widget --- .gitignore | 4 +- config/default.ts | 8 ++- package.json | 4 +- src/App.ts | 3 ++ src/wms/WMSMusicWidget.ts | 107 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 src/wms/WMSMusicWidget.ts diff --git a/.gitignore b/.gitignore index fe92ce5..462653f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ dist node_modules yarn.lock -yarn-error.log \ No newline at end of file +yarn-error.log + +config/local* \ No newline at end of file diff --git a/config/default.ts b/config/default.ts index 5836c65..94ee32c 100644 --- a/config/default.ts +++ b/config/default.ts @@ -10,5 +10,11 @@ export default { midi: { controller: 'Launchkey Mini MIDI 2', output: 'Launchkey Mini MIDI 2', - } + }, + wms: { + music_widget: { + ws: 'wss://watch-my.stream/widgets/music/stream', + token: 'default', + }, + }, }; \ No newline at end of file diff --git a/package.json b/package.json index 8db08b3..6b56e38 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "devDependencies": { "@types/node": "^14.0.23", + "@types/ws": "^7.2.6", "typescript": "^3.9.7" }, "dependencies": { @@ -17,6 +18,7 @@ "config": "^3.3.1", "jzz": "^1.0.8", "obs-websocket-js": "^4.0.1", - "ts-node": "^8.10.2" + "ts-node": "^8.10.2", + "ws": "^7.3.1" } } diff --git a/src/App.ts b/src/App.ts index 099792f..0779972 100644 --- a/src/App.ts +++ b/src/App.ts @@ -5,6 +5,7 @@ import jzz from "jzz"; import ObsWebSocket from "obs-websocket-js"; import LedState from "./LedState"; import ObsStateTracker from "./obs/ObsStateTracker"; +import WMSMusicWidget from "./wms/WMSMusicWidget"; export default class App { private obs: ObsWebSocket = new ObsWebSocket(); @@ -12,6 +13,7 @@ export default class App { private controls: MidiControl[] = []; private midiIn: any; private pendingStates: LedState[] = []; + private readonly wmsMusicWidget: WMSMusicWidget = new WMSMusicWidget(); public constructor() { @@ -25,6 +27,7 @@ export default class App { await this.initObs(); await this.obsStateTracker.init(this); await this.initMidi(); + await this.wmsMusicWidget.start(); } public async stop(): Promise { diff --git a/src/wms/WMSMusicWidget.ts b/src/wms/WMSMusicWidget.ts new file mode 100644 index 0000000..de7028d --- /dev/null +++ b/src/wms/WMSMusicWidget.ts @@ -0,0 +1,107 @@ +import config from "config"; +import WebSocket from "ws"; +import child_process from "child_process"; + +const metadataSeparator = '\n'; +/** + * Watch My Stream (watch-my.stream) music widget + */ +export default class WMSMusicWidget { + private readonly webSocketAddress: string = config.get('wms.music_widget.ws'); + private readonly token: string = config.get('wms.music_widget.token'); + private activeSocket?: WebSocket; + private checkInterval?: NodeJS.Timeout; + private lastSentData?: string; + + public constructor() { + } + + + public async start(): Promise { + if (!this.token || this.token === 'default') { + console.warn('WMS music widget not started due to missing token.'); + return; + } + + this.startWebSocket(); + + this.checkInterval = setInterval(async () => { + try { + if (this.activeSocket) { + const metadata = (await this.getInfo()).split(metadataSeparator); + const data = JSON.stringify({ + playing: metadata[0] === 'Playing', + author: metadata[1], + title: metadata[2], + album: metadata[3], + artUrl: metadata[4], + }); + if (this.lastSentData !== data) { + console.log('WMS music widget out:', data); + this.activeSocket.send(data); + this.lastSentData = data; + } + } + } catch (e) { + console.error(e); + } + }, 1000); + } + + public async stop(): Promise { + if (typeof this.checkInterval !== 'undefined') { + clearInterval(this.checkInterval); + this.checkInterval = undefined; + } + } + + private startWebSocket() { + const socket = new WebSocket(this.webSocketAddress, {}); + + socket.on('error', (event) => { + console.error(event); + }); + socket.on('close', (code, reason) => { + this.activeSocket = undefined; + console.log(`WMS music widget WS closed ${code} ${reason}. Retrying in 2s...`); + setTimeout(() => this.startWebSocket(), 2000); + }); + socket.on('open', () => { + socket.send(JSON.stringify({ + token: this.token, + type: 'emitter', + })); + }); + + socket.on('message', data => { + console.log('WMS music widget WebSocket ready!'); + this.activeSocket = socket; + }); + } + + private async getInfo(): Promise { + const format = [ + 'status', + 'artist', + 'title', + 'album', + 'mpris:artUrl', + ].map(s => '{{' + s + '}}').join(metadataSeparator); + return await this.runCommand(`playerctl metadata -f "${format}" | sed "s/open\\.spotify\\.com/i.scdn.co/"`); + } + + private async runCommand(command: string): Promise { + // console.info(`> ${command}`); + return new Promise((resolve, reject) => { + child_process.exec(command, {}, (err, stdout, stderr) => { + if (err) { + console.error(stderr); + reject(err); + return; + } + + resolve(stdout); + }); + }); + } +} \ No newline at end of file