From 22b74113b6cc53c3a1baba0e89532f7fd03736f9 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Thu, 28 Mar 2024 23:05:55 +0900 Subject: [PATCH] init webnowplaying --- package.json | 6 +- pnpm-lock.yaml | 23 ++++- src/plugins/webnowplaying/index.ts | 145 +++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 src/plugins/webnowplaying/index.ts diff --git a/package.json b/package.json index 18522ab4..a256e690 100644 --- a/package.json +++ b/package.json @@ -172,6 +172,7 @@ "node-html-parser": "6.1.12", "node-id3": "0.2.6", "peerjs": "1.5.2", + "reconnecting-websocket": "4.4.0", "semver": "7.6.0", "serve": "14.2.1", "simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9", @@ -181,6 +182,7 @@ "solid-transition-group": "0.2.3", "ts-morph": "22.0.0", "vudio": "2.1.1", + "ws": "8.16.0", "x11": "2.3.0", "youtubei.js": "9.1.0" }, @@ -192,6 +194,7 @@ "@types/howler": "2.2.11", "@types/html-to-text": "9.0.4", "@types/semver": "7.5.8", + "@types/ws": "8.5.10", "@typescript-eslint/eslint-plugin": "7.4.0", "bufferutil": "4.0.8", "builtin-modules": "3.3.0", @@ -217,8 +220,7 @@ "vite": "5.2.6", "vite-plugin-inspect": "0.8.3", "vite-plugin-resolve": "2.5.1", - "vite-plugin-solid": "2.10.2", - "ws": "8.16.0" + "vite-plugin-solid": "2.10.2" }, "auto-changelog": { "hideCredit": true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87d77971..006fc032 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -129,6 +129,9 @@ dependencies: peerjs: specifier: 1.5.2 version: 1.5.2 + reconnecting-websocket: + specifier: 4.4.0 + version: 4.4.0 semver: specifier: 7.6.0 version: 7.6.0 @@ -156,6 +159,9 @@ dependencies: vudio: specifier: 2.1.1 version: 2.1.1(patch_hash=7iux5msqpgl3octdmwy4uspwoe) + ws: + specifier: 8.16.0 + version: 8.16.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) x11: specifier: 2.3.0 version: 2.3.0 @@ -185,6 +191,9 @@ devDependencies: '@types/semver': specifier: 7.5.8 version: 7.5.8 + '@types/ws': + specifier: 8.5.10 + version: 8.5.10 '@typescript-eslint/eslint-plugin': specifier: 7.4.0 version: 7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.4.3) @@ -263,9 +272,6 @@ devDependencies: vite-plugin-solid: specifier: 2.10.2 version: 2.10.2(solid-js@1.8.16)(vite@5.2.6) - ws: - specifier: 8.16.0 - version: 8.16.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) packages: @@ -1747,6 +1753,12 @@ packages: dev: true optional: true + /@types/ws@8.5.10: + resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} + dependencies: + '@types/node': 20.11.30 + dev: true + /@types/yauzl@2.10.3: resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} requiresBuild: true @@ -5711,6 +5723,10 @@ packages: util-deprecate: 1.0.2 dev: true + /reconnecting-websocket@4.4.0: + resolution: {integrity: sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==} + dev: false + /redent@4.0.0: resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} engines: {node: '>=12'} @@ -6851,6 +6867,7 @@ packages: dependencies: bufferutil: 4.0.8 utf-8-validate: 6.0.3 + dev: false /x11@2.3.0: resolution: {integrity: sha512-Ep4DbqZkVHvZNVht+vvELcfdpGKnfh2kZuKdXqyZdtJx3UdvgUGrMQ9lwPNV33tDs86MF4YagC6+E2fZXikF6A==} diff --git a/src/plugins/webnowplaying/index.ts b/src/plugins/webnowplaying/index.ts new file mode 100644 index 00000000..aabd0d88 --- /dev/null +++ b/src/plugins/webnowplaying/index.ts @@ -0,0 +1,145 @@ +import { net } from 'electron'; + +import is from 'electron-is'; + +import { createPlugin } from '@/utils'; +import registerCallback from '@/providers/song-info'; +import { t } from '@/i18n'; + +import { WebSocket } from 'ws'; + +import ReconnectingWebSocket from 'reconnecting-websocket'; + +import type { RepeatMode } from '@/types/datahost-get-state'; + +interface Data { + player: string; + state: 'PLAYING' | 'PAUSED' | 'STOPPED'; + title: string; + artist: string; + album: string; + cover: string; + duration: string; + position: string; + volume: number; + rating: number; + repeat: 'ALL' | 'ONE' | 'NONE'; + shuffle: boolean; +} + +export default createPlugin({ + name: () => t('plugins.webnowplaying.name'), + description: () => t('plugins.webnowplaying.description'), + restartNeeded: true, + config: { + enabled: false, + }, + backend: { + liteMode: false, + data: { + player: 'YouTube Music', + state: 'STOPPED', + title: '', + artist: '', + album: '', + cover: '', + duration: '0:00', + // position and volume are fetched in sendUpdate() + position: '0:00', + volume: 100, + rating: 0, + repeat: 'NONE', + shuffle: false + } as Data, + start({ ipc }) { + const timeInSecondsToString = (timeInSeconds: number) => { + const timeInMinutes = Math.floor(timeInSeconds / 60); + if (timeInMinutes < 60) return `${timeInMinutes}:${Math.floor(timeInSeconds % 60).toString().padStart(2, '0')}`; + + return `${Math.floor(timeInMinutes / 60)}:${Math.floor(timeInMinutes % 60).toString().padStart(2, '0')}:${Math.floor(timeInSeconds % 60).toString().padStart(2, '0')}`; + }; + + const ws = new ReconnectingWebSocket('ws://localhost:8974', undefined, { + WebSocket: WebSocket, + maxEnqueuedMessages: 0, + }); + ws.onmessage = () => { + + }; + + const post = (data: Data) => { + const port = 1608; + const headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Access-Control-Allow-Headers': '*', + 'Access-Control-Allow-Origin': '*', + }; + const url = `http://127.0.0.1:${port}/`; + net + .fetch(url, { + method: this.liteMode ? 'OPTIONS' : 'POST', + headers, + keepalive: true, + body: this.liteMode ? undefined : JSON.stringify({ data }), + }) + .then(() => { + if (this.liteMode) { + this.liteMode = false; + console.debug( + `obs-tuna webserver at port ${port} is now accessible. disable lite mode`, + ); + post(data); + } + }) + .catch((error: { code: number; errno: number }) => { + if (!this.liteMode && is.dev()) { + console.debug( + `Error: '${ + error.code || error.errno + }' - when trying to access obs-tuna webserver at port ${port}. enable lite mode`, + ); + this.liteMode = true; + } + }); + }; + + ipc.on('ytmd:player-api-loaded', () => { + ipc.send('ytmd:setup-time-changed-listener'); + ipc.send('ytmd:setup-repeat-changed-listener'); + ipc.send('ytmd:setup-volume-changed-listener'); + }); + ipc.on('ytmd:time-changed', (t: number) => { + if (!this.data.title) { + return; + } + + this.data.position = timeInSecondsToString(t); + post(this.data); + }); + ipc.on('ytmd:repeat-changed', (mode: RepeatMode) => { + this.data.repeat = mode; + post(this.data); + }); + ipc.on('ytmd:volume-changed', (newVolume: number) => { + this.data.volume = newVolume; + post(this.data); + }); + + registerCallback((songInfo) => { + if (!songInfo.title && !songInfo.artist) { + return; + } + + this.data.duration = timeInSecondsToString(songInfo.songDuration); + this.data.position = timeInSecondsToString(songInfo.elapsedSeconds ?? 0); + this.data.cover = songInfo.imageSrc ?? ''; + this.data.title = songInfo.title; + this.data.artist = songInfo.artist; + this.data.state = songInfo.isPaused ? 'PAUSED' : 'PLAYING'; + this.data.album = songInfo.album ?? ''; + post(this.data); + }); + }, + }, +});