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); }); }); } }