From b7fb674d68c5b5185495e38d4b7231dcf07f9d7f Mon Sep 17 00:00:00 2001 From: Jozen Blue Martinez Date: Wed, 9 Feb 2022 05:40:57 +0800 Subject: [PATCH 1/4] Create WebNowPlaying plugin (#393) --- src/main/plugins/webNowPlaying.ts | 227 ++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 src/main/plugins/webNowPlaying.ts diff --git a/src/main/plugins/webNowPlaying.ts b/src/main/plugins/webNowPlaying.ts new file mode 100644 index 00000000..25673133 --- /dev/null +++ b/src/main/plugins/webNowPlaying.ts @@ -0,0 +1,227 @@ +import * as WebSocket from 'ws'; + +/** + * 0-pad a number. + * @param {Number} number + * @param {Number} length + * @returns String + */ +const pad = (number: number, length: number) => String(number).padStart(length, '0'); + +/** + * Convert seconds to a time string acceptable to Rainmeter + * https://github.com/tjhrulz/WebNowPlaying-BrowserExtension/blob/master/WebNowPlaying.js#L50-L59 + * @param {Number} timeInSeconds + * @returns String + */ +const convertTimeToString = (timeInSeconds: number) => { + const timeInMinutes = timeInSeconds / 60; + if (timeInMinutes < 60) { + return timeInMinutes + ":" + pad(timeInSeconds % 60, 2); + } + return timeInMinutes / 60 + ":" + pad(timeInMinutes % 60, 2) + ":" + pad(timeInSeconds % 60, 2); +} + +export default class WebNowPlaying { + /** + * Base Plugin Details (Eventually implemented into a GUI in settings) + */ + public name: string = 'WebNowPlaying'; + public description: string = 'Song info and playback control for the Rainmeter WebNowPlaying plugin.'; + public version: string = '1.0.0'; + public author: string = 'Zennn '; + + private _win: any; + private ws: any = null; + private wsapiConn: any = null; + private playerName: string = 'Cider'/* Apple Music */; + + constructor() { + console.debug(`[Plugin][${this.name}] Loading Complete.`); + } + + sendSongInfo(attributes: any) { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return; + + const fields = ['STATE', 'TITLE', 'ARTIST', 'ALBUM', 'COVER', 'DURATION', 'POSITION', 'VOLUME', 'REPEAT', 'SHUFFLE']; + fields.forEach((field) => { + try { + let value: any = ''; + switch (field) { + case 'STATE': + value = attributes.status ? 1 : 2; + break; + case 'TITLE': + value = attributes.name; + break; + case 'ARTIST': + value = attributes.artistName; + break; + case 'ALBUM': + value = attributes.albumName; + break; + case 'COVER': + value = attributes.artwork.url.replace('{w}', attributes.artwork.width).replace('{h}', attributes.artwork.height); + break; + case 'DURATION': + value = convertTimeToString(attributes.durationInMillis / 1000); + break; + case 'POSITION': + value = convertTimeToString((attributes.durationInMillis - attributes.remainingTime) / 1000); + break; + case 'VOLUME': + value = attributes.volume * 100; + break; + case 'REPEAT': + value = attributes.repeatMode; + break; + case 'SHUFFLE': + value = attributes.shuffleMode; + break; + } + this.ws.send(`${field}:${value}`); + } catch (error) { + if (this.ws.readyState === WebSocket.OPEN) { + this.ws.send(`Error:Error updating ${field} for ${this.playerName}`); + this.ws.send(`ErrorD:${error}`); + } + } + }); + } + + fireEvent(evt: any) { + if (!evt.data) return; + let value = ''; + if (evt.data.split(/ (.+)/).length > 1) { + value = evt.data.split(/ (.+)/)[1]; + } + const eventName = evt.data.split(' ')[0].toLowerCase(); + + try { + switch (eventName) { + case 'playpause': + this._win.webContents.executeJavaScript('MusicKitInterop.playPause()').catch(console.error); + break; + case 'next': + this._win.webContents.executeJavaScript('MusicKitInterop.next()').catch(console.error); + break; + case 'previous': + this._win.webContents.executeJavaScript('MusicKitInterop.previous()').catch(console.error); + break; + case 'setposition': + this._win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${parseFloat(value)})`); + break; + case 'setvolume': + this._win.webContents.executeJavaScript(`MusicKit.getInstance().volume = ${parseFloat(value) / 100}`); + break; + case 'repeat': + this._win.webContents.executeJavaScript('wsapi.toggleRepeat()').catch(console.error); + break; + case 'shuffle': + this._win.webContents.executeJavaScript('wsapi.toggleShuffle()').catch(console.error); + break; + case 'togglethumbsup': + // not implemented + break; + case 'togglethumbsdown': + // not implemented + break; + case 'rating': + // not implemented + break; + } + } catch (error) { + console.debug(error); + if (this.ws.readyState === WebSocket.OPEN) { + this.ws.send(`Error:Error sending event to ${this.playerName}`); + this.ws.send(`ErrorD:${error}`); + } + } + } + + /** + * Runs on app ready + */ + onReady(win: any) { + this._win = win; + + // Connect to Rainmeter plugin and retry on disconnect. + const init = () => { + try { + this.ws = new WebSocket('ws://127.0.0.1:8974/'); + let retry: NodeJS.Timeout; + this.ws.onopen = (() => { + console.info('[WebNowPlaying] Connected to Rainmeter'); + this.ws.send(`PLAYER:${this.playerName}`); + }).bind(this); + + this.ws.onclose = () => { + clearTimeout(retry); + retry = setTimeout(init, 2000); + }; + + this.ws.onerror = () => { + clearTimeout(retry); + this.ws.close(); + }; + + this.ws.onmessage = this.fireEvent?.bind(this); + } catch (error) { + console.error(error); + } + }; + + init(); + + // Connect to wsapi. Only used to update progress. + try { + this.wsapiConn = new WebSocket('ws://127.0.0.1:26369/'); + + this.wsapiConn.onopen = () => { + console.info('[WebNowPlaying] Connected to wsapi'); + }; + + this.wsapiConn.onmessage = (evt: { data: string; }) => { + const response = JSON.parse(evt.data); + if (response.type === 'playbackStateUpdate') { + this.sendSongInfo(response.data); + } + }; + } catch (error) { + console.error(error); + } + + console.debug(`[Plugin][${this.name}] Ready.`); + } + + /** + * Runs on app stop + */ + onBeforeQuit() { + if (this.ws) { + this.ws.send('STATE:0'); + this.ws.onclose = null; // disable onclose handler first to stop it from retrying + this.ws.close(); + } + if (this.wsapiConn) { + this.wsapiConn.close(); + } + console.debug(`[Plugin][${this.name}] Stopped.`); + } + + /** + * Runs on playback State Change + * @param attributes Music Attributes (attributes.status = current state) + */ + onPlaybackStateDidChange(attributes: any) { + this.sendSongInfo(attributes); + } + + /** + * Runs on song change + * @param attributes Music Attributes + */ + onNowPlayingItemDidChange(attributes: any) { + this.sendSongInfo(attributes); + } +} From 195b4c93fbb38016a1f994345a5814fdff5a2e92 Mon Sep 17 00:00:00 2001 From: vapormusic Date: Wed, 9 Feb 2022 00:56:59 +0700 Subject: [PATCH 2/4] encode uri --- src/renderer/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/index.js b/src/renderer/index.js index 5a052a96..4ddf7905 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -2891,7 +2891,7 @@ const app = new Vue({ return } //this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${this.search.term}` - this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${this.search.term}`, { + this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${encodeURIComponent(this.search.term)}`, { types: "activities,albums,apple-curators,artists,curators,editorial-items,music-movies,music-videos,playlists,songs,stations,tv-episodes,uploaded-videos,record-labels", "relate[editorial-items]": "contents", "include[editorial-items]": "contents", From dc386c5bd410ddd13348057acf0abaf2f3058fc9 Mon Sep 17 00:00:00 2001 From: vapormusic Date: Wed, 9 Feb 2022 00:57:57 +0700 Subject: [PATCH 3/4] typo --- src/renderer/views/components/audio-settings.ejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/views/components/audio-settings.ejs b/src/renderer/views/components/audio-settings.ejs index 379cf9ae..512db50e 100644 --- a/src/renderer/views/components/audio-settings.ejs +++ b/src/renderer/views/components/audio-settings.ejs @@ -13,7 +13,7 @@
{{app.getLz('term.equalizer')}}
@@ -38,7 +38,7 @@ app.modals.equalizer = true app.modals.audioSettings = false }, - openSpacialAudio() { + openSpatialAudio() { if(app.cfg.audio.spatial) { app.modals.spatialProperties = true app.modals.audioSettings = false From 43bf12d2847ab3ce1a0e463f8d37aa7b26b66bf8 Mon Sep 17 00:00:00 2001 From: Amaru8 <52407090+Amaru8@users.noreply.github.com> Date: Wed, 9 Feb 2022 02:17:02 +0100 Subject: [PATCH 4/4] Translation update (#395) * Update Hungarian (hu_HU) language file * Temporary fix for Hindi language i18n info --- src/i18n/hi_IN.jsonc | 4 ++-- src/i18n/hu_HU.jsonc | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/i18n/hi_IN.jsonc b/src/i18n/hi_IN.jsonc index ad96e8f3..e1127d2c 100644 --- a/src/i18n/hi_IN.jsonc +++ b/src/i18n/hi_IN.jsonc @@ -1,8 +1,8 @@ { // i18n Info - "i18n.languageName": "English (US)", // name of language in native language - "i18n.languageNameEnglish": "English (US)", // name of language in English + "i18n.languageName": "हिन्दी", // name of language in native language + "i18n.languageNameEnglish": "Hindi", // name of language in English "i18n.category": "main", // main = real language, fun = fun community languages "i18n.authors": "@maikirakiwi @vringster", // Authors, if you contribute to this file feel free to add your name seperated with a space diff --git a/src/i18n/hu_HU.jsonc b/src/i18n/hu_HU.jsonc index 28882427..382679d7 100644 --- a/src/i18n/hu_HU.jsonc +++ b/src/i18n/hu_HU.jsonc @@ -106,7 +106,7 @@ "term.time.second": "másodperc", "term.fullscreenView": "Teljes képernyős mód", "term.defaultView": "Alapértelmezett nézet", - "term.spacializedAudioSetting": "Térbeli hang", + "term.audioSettings": "Hangbeállítások", "term.clearAll": "Összes törlése", "term.recentStations": "Nemrég játszott", "term.language": "Nyelv", @@ -131,9 +131,9 @@ "term.sharedPlaylists": "Megosztott lejátszási listák", // Search Results "term.people": "Profilok", // Search Results "term.newpreset.name": "New EQ Preset Name", // Equalizer Preset - "term.addedpreset": "Preset hozzáadva", - "term.deletepreset.warn": "Are you sure you want to delete this preset?", - "term.deletedpreset": "Preset törölve", + "term.addedpreset": "Előbeállítás hozzáadva", + "term.deletepreset.warn": "Biztos törölni szeretnéd ezt az előbeállítást?", + "term.deletedpreset": "Előbeállítás törölve", "term.musicVideos": "Videóklipek", // Search page friendlyTypes "term.stations": "Állomások", "term.curators": "Curators", @@ -229,7 +229,11 @@ "settings.option.general.language.unsorted": "Azonosítatlan", // Update Cider - "settings.option.general.updateCider": "A Cider frissítése", // Button + "settings.option.general.updateCider": "Cider frissítése", // Button. Refer to term.check for the check button + "settings.option.general.updateCider.branch": "Verzió kiválasztása", // Dropdown + "settings.option.general.updateCider.branch.description": "Válaszd ki a Cider melyik verziójára szeretnél frissíteni", + "settings.option.general.updateCider.branch.main": "Normál", + "settings.option.general.updateCider.branch.develop": "Fejlesztői", // Settings - Audio "settings.header.audio": "Hang", @@ -260,7 +264,7 @@ // Settings - Visual "settings.header.visual": "Vizuális", "settings.header.visual.description": "A Cider vizuális beállításainak módosítása.", - "settings.option.visual.windowBackgroundStyle": "Ablak háttér stílusa", // Toggle + "settings.option.visual.windowBackgroundStyle": "Ablakháttér stílusa", // Toggle "settings.header.visual.windowBackgroundStyle.none": "Sehol", "settings.header.visual.windowBackgroundStyle.artwork": "Borító", "settings.header.visual.windowBackgroundStyle.image": "Kép", @@ -315,7 +319,7 @@ // Settings - Experimental "settings.header.experimental": "Kísérleti", "settings.header.experimental.description": "A Cider kísérleti beállításainak módosítása.", - "settings.option.experimental.compactUI": "Kompakt UI", // Toggle + "settings.option.experimental.compactUI": "Kompakt felület", // Toggle "settings.option.experimental.close_button_hide": "A bezárás gomb rejtse el az alkalmazást", "settings.option.experimental.copy_log": "Napló másolása a vágólapra", "settings.option.experimental.inline_playlists": "Inline Playlists and Albums",