Integrate WMS music widget

This commit is contained in:
Alice Gaudon 2020-07-19 18:20:24 +02:00
parent 29bd7822a3
commit 347e5348b4
5 changed files with 123 additions and 3 deletions

4
.gitignore vendored
View File

@ -2,4 +2,6 @@
dist dist
node_modules node_modules
yarn.lock yarn.lock
yarn-error.log yarn-error.log
config/local*

View File

@ -10,5 +10,11 @@ export default {
midi: { midi: {
controller: 'Launchkey Mini MIDI 2', controller: 'Launchkey Mini MIDI 2',
output: 'Launchkey Mini MIDI 2', output: 'Launchkey Mini MIDI 2',
} },
wms: {
music_widget: {
ws: 'wss://watch-my.stream/widgets/music/stream',
token: 'default',
},
},
}; };

View File

@ -10,6 +10,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^14.0.23", "@types/node": "^14.0.23",
"@types/ws": "^7.2.6",
"typescript": "^3.9.7" "typescript": "^3.9.7"
}, },
"dependencies": { "dependencies": {
@ -17,6 +18,7 @@
"config": "^3.3.1", "config": "^3.3.1",
"jzz": "^1.0.8", "jzz": "^1.0.8",
"obs-websocket-js": "^4.0.1", "obs-websocket-js": "^4.0.1",
"ts-node": "^8.10.2" "ts-node": "^8.10.2",
"ws": "^7.3.1"
} }
} }

View File

@ -5,6 +5,7 @@ import jzz from "jzz";
import ObsWebSocket from "obs-websocket-js"; import ObsWebSocket from "obs-websocket-js";
import LedState from "./LedState"; import LedState from "./LedState";
import ObsStateTracker from "./obs/ObsStateTracker"; import ObsStateTracker from "./obs/ObsStateTracker";
import WMSMusicWidget from "./wms/WMSMusicWidget";
export default class App { export default class App {
private obs: ObsWebSocket = new ObsWebSocket(); private obs: ObsWebSocket = new ObsWebSocket();
@ -12,6 +13,7 @@ export default class App {
private controls: MidiControl[] = []; private controls: MidiControl[] = [];
private midiIn: any; private midiIn: any;
private pendingStates: LedState[] = []; private pendingStates: LedState[] = [];
private readonly wmsMusicWidget: WMSMusicWidget = new WMSMusicWidget();
public constructor() { public constructor() {
@ -25,6 +27,7 @@ export default class App {
await this.initObs(); await this.initObs();
await this.obsStateTracker.init(this); await this.obsStateTracker.init(this);
await this.initMidi(); await this.initMidi();
await this.wmsMusicWidget.start();
} }
public async stop(): Promise<void> { public async stop(): Promise<void> {

107
src/wms/WMSMusicWidget.ts Normal file
View File

@ -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<string>('wms.music_widget.ws');
private readonly token: string = config.get<string>('wms.music_widget.token');
private activeSocket?: WebSocket;
private checkInterval?: NodeJS.Timeout;
private lastSentData?: string;
public constructor() {
}
public async start(): Promise<void> {
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<void> {
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<string> {
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<string> {
// console.info(`> ${command}`);
return new Promise<string>((resolve, reject) => {
child_process.exec(command, {}, (err, stdout, stderr) => {
if (err) {
console.error(stderr);
reject(err);
return;
}
resolve(stdout);
});
});
}
}