Integrate WMS music widget
This commit is contained in:
parent
29bd7822a3
commit
347e5348b4
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,3 +3,5 @@ dist
|
|||||||
node_modules
|
node_modules
|
||||||
yarn.lock
|
yarn.lock
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
|
||||||
|
config/local*
|
@ -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',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
107
src/wms/WMSMusicWidget.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user