diff --git a/src/i18n/el_GR.jsonc b/src/i18n/el_GR.jsonc index 84e1762d..c277c8aa 100644 --- a/src/i18n/el_GR.jsonc +++ b/src/i18n/el_GR.jsonc @@ -1,6 +1,9 @@ { // App info "app.name": "Cider", + + "date.format": "${d} ${m}, ${y}", + // Dialogs "dialog.cancel": "Ακύρωση", "dialog.ok": "ΟΚ", @@ -9,8 +12,8 @@ "notification.updatingLibrarySongs": "Ενημέρωση βιβλιοθήκης τραγουδιών...", "notification.updatingLibraryAlbums": "Ενημέρωση βιβλιοθήκης άλμπουμ...", "notification.updatingLibraryArtists": "Ενημέρωση βιβλιοθήκης καλλιτεχνών...", - // Terms + "term.appleInc": "Apple Inc.", "term.appleMusic": "Apple Music", "term.applePodcasts": "Apple Podcasts", "term.itunes": "iTunes", @@ -60,6 +63,7 @@ "term.viewAs": "Προβολή Ως", "term.viewAs.coverArt": "Εξώφυλλο", "term.viewAs.list": "Λίστα", + "term.size": "Μέγεθος", "term.size.normal": "Κανονικό", "term.size.compact": "Συμπαγή", "term.enable": "Ενεργοποίηση", @@ -67,17 +71,58 @@ "term.enabled": "Ενεργοποιημένο", "term.disabled": "Απενεργοποιημένο", "term.connect": "Σύνδεση", + "term.connecting": "Γίνεται Σύνδεση", + "term.disconnect": "Αποσύνδεση", + "term.authed": "Επικυρωμένο", + "term.confirm": "Σίγουρα;", + "term.more": "Περισσότερα", + "term.less": "Λιγότερα", + "term.showMore": "Εμφάνιση περισσότερων", + "term.showLess": "Εμφάνιση λιγότερων", + "term.topSongs" : "Κορυφαία Τραγούδια", + "term.latestReleases": "Τελευταίες Κυκλοφορίες", + "term.time.added": "Προστέθηκε", + "term.time.released": "Κυκλοφόρησε", + "term.time.updated": "Ενημερώθηκε", + "term.fullscreenView": "Πλήρης οθόνη", + "term.defaultView": "Κανονική οθόνη", + "term.spacializedAudioSetting": "Χωρική Ρύθμιση Ήχου", + "term.clearAll": "Εκκαθάριση Όλων", + "term.recentStations": "Πρόσφατοι Σταθμοί", + "term.language": "Γλώσσα", + "term.noLyrics": "Φόρτωση... / Δεν βρέθηκαν στίχοι./ Ορχηστικό.", + "term.copyright": "Copyright", + "term.rightsReserved": "Όλα τα δικαιώματα διατηρούνται.", + "term.sponsor": "Χορήγησε αυτό το έργο", + "term.ciderTeam": "Ομάδα Cider", + "term.developer": "Προγραμματιστής", + "term.socialTeam": "Κοινωνική Ομάδα", + "term.contributors": "Συνεισφέροντες", // Home "home.title": "Αρχική", "home.recentlyPlayed": "Έπαιξαν Πρόσφατα", "home.recentlyAdded": "Πρόσφατες Προσθήκες", "home.artistsFeed": "Ροή των Καλλιτεχνών σου", + "home.artistsFeed.noArtist": "Ακολούθησε μερικούς καλλιτέχνες πρώτα και οι τελευταίες κυκλοφορίες τους θα εμφανίζονται εδώ", "home.madeForYou": "Δημιουργήθηκε Για Εσάς", "home.friendsListeningTo": "Οι Φίλοι σου Ακούν", "home.followedArtists": "Καλλιτέχνες που Ακολουθείτε", // Errors "error.appleMusicSubRequired": "Το Apple Music απαιτεί μια συνδρομή.", + "error.connectionError": "Δεν είναι δυνατή η σύνδεση με το Apple Music.", + "error.noResults": "Κανένα αποτέλεσμα.", + "error.noResults.description": "Δοκιμάστε μια νέα αναζήτηση.", + + //Podcasts + "podcast.followOnCider": "Ακολούθηση στο Cider", + "podcast.followedOnCider": "Ακολουθείτε στο Cider", + "podcast.subscribeOnItunes": "Συνδρομή στο iTunes", + "podcast.subscribedOnItunes": "Συνδρομητής στο iTunes", + "podcast.itunesStore": "iTunes Store", + "podcast.episodes": "Επεισόδια", + "podcast.playEpisode": "Αναπαραγωγή Επεισοδίου", + "podcast.website": "Ιστότοπος Podcast", // Actions "action.addToLibrary": "Προσθήκη στη Βιβλιοθήκη", @@ -92,6 +137,7 @@ "action.removeFromQueue.success": "Αφαιρέθηκε από την Ουρά", "action.removeFromQueue.error": "Σφάλμα Αφαίρεσης από την Ουρά", "action.addToPlaylist": "Προσθήκη σε Λίστα", + "action.removeFromPlaylist": "Αφαίρεση από Λίστα", "action.addToFavorites": "Προσθήκη στα Αγαπημένα", "action.follow": "Ακολούθηση", "action.follow.success": "Ακολουθήθηκε", @@ -112,6 +158,7 @@ "action.dislike": "Δεν μου αρέσει", "action.undoDislike": "Αναίρεση \"Δεν μου αρέσει\"", "action.showWebRemoteQR": "Εμφάνιση Web Remote QR", + // Settings - Audio "settings.header.audio": "Ήχος", "settings.header.audio.description": "Προσαρμογή ρυθμίσεων ήχου για το Cider.", @@ -140,9 +187,11 @@ "settings.header.visual.animatedArtworkQuality.low": "Χαμηλή", "settings.header.visual.animatedArtworkQuality.medium": "Μέτρια", "settings.header.visual.animatedArtworkQuality.high": "Υψηλή", - "settings.header.visual.animatedArtworkQuality.extreme": "Πολύ Υψηλή", + "settings.header.visual.animatedArtworkQuality.veryHigh": "Πολύ Υψηλή", + "settings.header.visual.animatedArtworkQuality.extreme": "Ακραία", "settings.option.visual.animatedWindowBackground": "Κινούμενο Φόντο Παραθύρου", // Toggle "settings.option.visual.hardwareAcceleration": "Επιτάχυνση Υλικού", // Dropdown + "settings.option.visual.hardwareAcceleration.description": "Απαιτεί επανεκκίνηση", "settings.header.visual.hardwareAcceleration.default": "Προεπιλογή", "settings.header.visual.hardwareAcceleration.webGPU": "WebGPU", // Refer to term.disabled for the disabled option @@ -168,6 +217,9 @@ "settings.header.connectivity.discordRPC.appleMusic": "Εμφάνιση ως 'Apple Music'", "settings.option.connectivity.discordRPC.clearOnPause": "Εκκαθάριση του Discord Rich Presence στην Παύση", // Toggle "settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling", // Option to Connect + "settings.option.connectivity.lastfmScrobble.delay": "Καθυστέρηση LastFM Scrobble (%)", + "settings.option.connectivity.lastfmScrobble.nowPlaying": "Ενεργοποίηση LastFM \"Now Playing\"", + "settings.option.connectivity.lastfmScrobble.removeFeatured": "Αφαίρεση καλλιτεχνών feature από τον τίτλο του τραγουδιού (LastFM)", // Refer to term.connect for the connect button // Settings - Experimental @@ -176,7 +228,32 @@ "settings.option.experimental.compactUI": "Συμπαγής Διεπαφή", // Toggle // Refer to term.disabled & term.enabled + // Spatialization Menu + "spatial.spatialProperties" : "Χωρικές Ιδιότητες", + "spatial.width" : "Πλάτος", + "spatial.height" : "Ύψος", + "spatial.depth" : "Βάθος", + "spatial.roomMaterials" : "Υλικά Δωματίου", + "spatial.roomDimensions" : "Διαστάσεις Δωματίου", + "spatial.roomPositions" : "Θέσεις Δωματίου", + "spatial.setDimensions" : "Ορισμός Διαστάσεων", + "spatial.setPositions" : "Ορισμός Θέσεων", + "spatial.up" : "Πάνω", + "spatial.front" : "Πρόσοψη", + "spatial.left" : "Αριστερά", + "spatial.right" : "Δεξιά", + "spatial.back" : "Πίσω Όψη", + "spatial.down" : "Κάτω", + "spatial.listener" : "Ακροατής", + "spatial.audioSource" : "Πηγή Ήχου", + + // Settings - Unfinished + "settings.header.unfinished": "Ημιτελής", + // Web Remote "remote.web.title": "Cider Remote", - "remote.web.description": "Σαρώστε τον κωδικό QR για σύζευξη του Cider με το κινητό σας" -} + "remote.web.description": "Σαρώστε τον κωδικό QR για σύζευξη του Cider με το κινητό σας", + + //About + "about.thanks": "Μεγάλα ευχαριστώ στην Ομάδα Cider Collective και σε όλους τους συνεισφέροντές μας." +} \ No newline at end of file diff --git a/src/i18n/en_US.jsonc b/src/i18n/en_US.jsonc index cffdda4a..fa095aff 100644 --- a/src/i18n/en_US.jsonc +++ b/src/i18n/en_US.jsonc @@ -98,6 +98,8 @@ "term.developer": "Developer", "term.socialTeam": "Social Team", "term.contributors": "Contributors", + "term.equalizer": "Equalizer", + "term.reset": "Reset", // Home "home.title": "Home", diff --git a/src/i18n/pt_BR.jsonc b/src/i18n/pt_BR.jsonc index e11a1f44..76ff31c5 100644 --- a/src/i18n/pt_BR.jsonc +++ b/src/i18n/pt_BR.jsonc @@ -30,7 +30,7 @@ "term.search": "Procurar", "term.library": "Biblioteca", "term.listenNow": "Ouça Agora", - "term.browse": "Pesquisar", + "term.browse": "Explorar", "term.radio": "Radio", "term.recentlyAdded": "Adicionado Recentemente", "term.songs": "Músicas", @@ -72,6 +72,8 @@ "term.enabled": "Ativado", "term.disabled": "Desativado", "term.connect": "Conectar", + "term.disconnect": "Desconectar", + "term.connecting": "Conectando", "term.confirm": "Confirmar ?", "term.more": "Mais", "term.less": "Menos", @@ -86,6 +88,8 @@ "term.defaultView": "Visualização Normal", "term.spacializedAudioSetting": "Definições de Audio Espacial", "term.clearAll": "Limpar Tudo", + "term.language": "Idioma", + "term.recentStations": "Estações Recentes", // Home "home.title": "Inicio", @@ -160,11 +164,15 @@ "settings.header.visual.animatedArtworkQuality.low": "Baixa", "settings.header.visual.animatedArtworkQuality.medium": "Media", "settings.header.visual.animatedArtworkQuality.high": "Alta", + "settings.header.visual.animatedArtworkQuality.veryHigh": "Muito Alta", "settings.header.visual.animatedArtworkQuality.extreme": "Extrema", "settings.option.visual.animatedWindowBackground": "Fundo de Janela Animado", // Toggle "settings.option.visual.hardwareAcceleration": "Acelaração no Hardware", // Dropdown + "settings.option.visual.hardwareAcceleration.description": "Necessário reiniciar a aplicação", "settings.header.visual.hardwareAcceleration.default": "Normal", "settings.header.visual.hardwareAcceleration.webGPU": "WebGPU", + + // Refer to term.disabled for the disabled option "settings.option.visual.showPersonalInfo": "Mostrar Informaçoes Pessoais", // Toggle // Settings - General (Reserved) @@ -186,7 +194,7 @@ // Refer to term.disabled for the disabled option "settings.header.connectivity.discordRPC.cider": "Mostrar como 'Cider'", "settings.header.connectivity.discordRPC.appleMusic": "Mostrar como 'Apple Music'", - "settings.header.connectivity.discordRPC.clearOnPause": "Apagar Discord Rich Presence quando estiver pausado", // Toggle + "settings.option.connectivity.discordRPC.clearOnPause": "Apagar Discord Rich Presence quando estiver pausado", // Toggle "settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling", // Option to Connect "settings.option.connectivity.lastfmScrobble.delay": "Atraso dos Scrobbles do LastFM (%)", "settings.option.connectivity.lastfmScrobble.nowPlaying": "Ativar LastFM Now Playing", diff --git a/src/i18n/zh_HK.jsonc b/src/i18n/zh_HK.jsonc index 640b80d1..9c057a89 100644 --- a/src/i18n/zh_HK.jsonc +++ b/src/i18n/zh_HK.jsonc @@ -13,6 +13,7 @@ "notification.updatingLibraryAlbums": "正在更新資料庫的專輯...", "notification.updatingLibraryArtists": "正在更新資料庫的藝人...", // Terms + "term.appleInc": "Apple Inc.", "term.appleMusic": "Apple Music", // Follows brand term "term.applePodcasts": "Apple Podcasts", // Follows brand term "term.itunes": "iTunes", // Follows brand term @@ -24,7 +25,7 @@ "term.login": "登入", "term.about": "關於", "term.privateSession": "私人模式", - "term.queue": "播放清單", + "term.queue": "待播清單", "term.search": "搜尋", "term.library": "資料庫", "term.listenNow": "立即聆聽", @@ -70,6 +71,9 @@ "term.enabled": "已啟用", "term.disabled": "已停用", "term.connect": "連結", + "term.connecting": "連結中", + "term.disconnect": "取消連結", + "term.authed": "已授權", "term.confirm": "確認?", "term.more": "更多", "term.less": "較少", @@ -82,10 +86,18 @@ "term.time.updated": "更新於", "term.fullscreenView": "全螢幕檢視", "term.defaultView": "一般檢視", - "term.spacializedAudioSetting": "音频空间化设置", + "term.spacializedAudioSetting": "空間音訊設定", "term.clearAll": "清空", "term.recentStations": "最近播放的頻道", "term.language": "語言", + "term.noLyrics": "加載中... / 找不到歌詞。/ 純音樂。", + "term.copyright": "Copyright", + "term.rightsReserved": "保留一切權利。", + "term.sponsor": "贊助這個項目", + "term.ciderTeam": "Cider 團隊", + "term.developer": "開發者", + "term.socialTeam": "社交團隊", + "term.contributors": "貢獻者", // Home "home.title": "主頁", @@ -98,21 +110,32 @@ "home.followedArtists": "追蹤的藝人", // Errors "error.appleMusicSubRequired": "需要訂閱Apple Music以使用Cider", - "error.connectionError": "There was a problem connecting to Apple Music.", - "error.noResults": "No Results.", - "error.noResults.description": "Try a new search.", + "error.connectionError": "無法連接到 Apple Music。", + "error.noResults": "沒有結果", + "error.noResults.description": "請嘗試新的搜尋內容。", + + //Podcasts + "podcast.followOnCider": "在Cider上追蹤", + "podcast.followedOnCider": "已在Cider上追蹤", + "podcast.subscribeOnItunes": "在iTunes上訂閱", + "podcast.subscribedOnItunes": "已在iTunes上訂閱", + "podcast.itunesStore": "iTunes Store", + "podcast.episodes": "單集", + "podcast.playEpisode": "播放單集", + "podcast.website": "Podcast 網頁", + // Actions "action.addToLibrary": "加入資料庫", "action.addToLibrary.success": "成功加入資料庫", "action.addToLibrary.error": "加入資料庫的過程發生錯誤", "action.removeFromLibrary": "從資料庫刪除", "action.removeFromLibrary.success": "已從資料庫刪除", - "action.addToQueue": "加入播放清單", - "action.addToQueue.success": "成功加入播放清單", - "action.addToQueue.error": "加入播放清單的過程發生錯誤", - "action.removeFromQueue": "從播放清單刪除", - "action.removeFromQueue.success": "已從播放清單刪除", - "action.removeFromQueue.error": "從播放清單刪除的過程中發生錯誤", + "action.addToQueue": "加入待播清單", + "action.addToQueue.success": "成功加入待播清單", + "action.addToQueue.error": "加入待播清單的過程發生錯誤", + "action.removeFromQueue": "從待播清單刪除", + "action.removeFromQueue.success": "已從待播清單刪除", + "action.removeFromQueue.error": "從待播清單刪除的過程中發生錯誤", "action.addToPlaylist": "加至播放列表", "action.removeFromPlaylist": "從播放列表刪除", "action.addToFavorites": "加至收藏", @@ -194,6 +217,9 @@ "settings.header.connectivity.discordRPC.appleMusic": "顯示為'Apple Music'", "settings.option.connectivity.discordRPC.clearOnPause": "暫停時清除 Discord 狀態", // Toggle "settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling 記錄", // Option to Connect + "settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble 延遲 (%)", + "settings.option.connectivity.lastfmScrobble.nowPlaying": "啟用 LastFM 正在播放", + "settings.option.connectivity.lastfmScrobble.removeFeatured": "Remove featuring artists from song title (LastFM)", // Refer to term.connect for the connect button // Settings - Experimental @@ -202,10 +228,32 @@ "settings.option.experimental.compactUI": "緊凑型 UI", // Toggle // Refer to term.disabled & term.enabled + // Spatialization Menu + "spatial.spatialProperties" : "空間音訊屬性", + "spatial.width" : "闊度", + "spatial.height" : "高度", + "spatial.depth" : "深度", + "spatial.roomMaterials" : "空間材質", + "spatial.roomDimensions" : "空間大小", + "spatial.roomPositions" : "空間位置", + "spatial.setDimensions" : "大小設定", + "spatial.setPositions" : "位置設定", + "spatial.up" : "上方", + "spatial.front" : "前方", + "spatial.left" : "左方", + "spatial.right" : "右方", + "spatial.back" : "後方", + "spatial.down" : "下方", + "spatial.listener" : "觀眾", + "spatial.audioSource" : "音源", + // Settings - Unfinished "settings.header.unfinished": "未完成", // Web Remote "remote.web.title": "遙距控制 Cider", "remote.web.description": "掃描以下的二維碼以控制 Cider", -} + + //About + "about.thanks": "感謝Cider Collective團隊以及所有貢獻者所作出的貢獻。" +} \ No newline at end of file diff --git a/src/i18n/zh_TW.jsonc b/src/i18n/zh_TW.jsonc index 5eefc2be..2c843b37 100644 --- a/src/i18n/zh_TW.jsonc +++ b/src/i18n/zh_TW.jsonc @@ -82,7 +82,7 @@ "term.time.updated": "更改于", "term.fullscreenView": "全螢幕顯示", "term.defaultView": "預設顯示", - "term.spacializedAudioSetting": "音频空间化设置", + "term.spacializedAudioSetting": "音頻空間化設置", "term.clearAll": "清空", "term.recentStations": "最近收聽的廣播", "term.language": "語言", @@ -110,9 +110,9 @@ "action.addToQueue": "加入待播清單", "action.addToQueue.success": "成功加入待播清單", "action.addToQueue.error": "加入待播清單的過程發生錯誤", - "action.removeFromQueue": "從代播清單刪除", - "action.removeFromQueue.success": "已從代播清單刪除", - "action.removeFromQueue.error": "從代播清單刪除的過程發生錯誤", + "action.removeFromQueue": "從待播清單刪除", + "action.removeFromQueue.success": "已從待播清單刪除", + "action.removeFromQueue.error": "從待播清單刪除的過程發生錯誤", "action.addToPlaylist": "加入播放列表", "action.removeFromPlaylist": "從播放列表刪除", "action.addToFavorites": "加入我的最愛", diff --git a/src/main/base/app.ts b/src/main/base/app.ts index b79f9963..beb61a23 100644 --- a/src/main/base/app.ts +++ b/src/main/base/app.ts @@ -50,7 +50,7 @@ export class AppEvents { /*********************************************************************************************************************** * Commandline arguments **********************************************************************************************************************/ - switch (store.get("visual.hw_acceleration")) { + switch (store.visual.hw_acceleration) { default: case "default": electron.app.commandLine.appendSwitch('enable-accelerated-mjpeg-decode') @@ -75,6 +75,10 @@ export class AppEvents { break; } + if (process.platform === "linux") { + electron.app.commandLine.appendSwitch('disable-features', 'MediaSessionService'); + } + /*********************************************************************************************************************** * Protocols **********************************************************************************************************************/ diff --git a/src/main/base/plugins.ts b/src/main/base/plugins.ts index 637f6bd2..92663ab9 100644 --- a/src/main/base/plugins.ts +++ b/src/main/base/plugins.ts @@ -5,10 +5,11 @@ import * as electron from 'electron' export default class PluginHandler { private basePluginsPath = path.join(__dirname, '../plugins'); private userPluginsPath = path.join(electron.app.getPath('userData'), 'plugins'); - private pluginsList: any = {}; - - constructor() { + private readonly pluginsList: any = {}; + private readonly _store: any; + constructor(config: any) { + this._store = config; this.pluginsList = this.getPlugins(); } @@ -23,7 +24,7 @@ export default class PluginHandler { if (plugins[file] || plugin.name in plugins) { console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`); } else { - plugins[file] = new plugin(electron.app); + plugins[file] = new plugin(electron.app, this._store); } } }); @@ -38,7 +39,7 @@ export default class PluginHandler { if (plugins[file] || plugin in plugins) { console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`); } else { - plugins[file] = new plugin(electron.app); + plugins[file] = new plugin(electron.app, this._store); } } }); diff --git a/src/main/base/store.ts b/src/main/base/store.ts index bc495822..143c1384 100644 --- a/src/main/base/store.ts +++ b/src/main/base/store.ts @@ -2,7 +2,7 @@ import * as Store from 'electron-store'; import * as electron from "electron"; export class ConfigStore { - public store: Store | undefined; + private _store: Store; private defaults: any = { "general": { @@ -49,6 +49,18 @@ export class ConfigStore { "down": 'acoustic-ceiling-tiles', "up": 'acoustic-ceiling-tiles', } + }, + "equalizer": { + '60' : 0, + '170': 0 , + '310': 0 , + '600': 0 , + '1000': 0 , + '3000': 0 , + '6000': 0 , + '12000': 0 , + '14000': 0 , + '16000': 0 , } }, "visual": { @@ -84,14 +96,26 @@ export class ConfigStore { private migrations: any = {} constructor() { - this.store = new Store({ + this._store = new Store({ name: 'cider-config', defaults: this.defaults, migrations: this.migrations, }); - this.store.set(this.mergeStore(this.defaults, this.store.store)) - this.ipcHandler(this.store); + this._store.set(this.mergeStore(this.defaults, this._store.store)) + this.ipcHandler(this._store); + } + + get store() { + return this._store.store; + } + + get(key: string) { + return this._store.get(key); + } + + set(key: string, value: any) { + this._store.set(key, value); } /** diff --git a/src/main/base/win.ts b/src/main/base/win.ts index 5ce72d6d..da304567 100644 --- a/src/main/base/win.ts +++ b/src/main/base/win.ts @@ -15,10 +15,10 @@ import {wsapi} from "./wsapi"; import * as jsonc from "jsonc"; export class Win { - win: any | undefined = null; - app: any | undefined = null; - store: any | undefined = null; - devMode: boolean = !electron.app.isPackaged; + private win: any | undefined = null; + private app: any | undefined = null; + private store: any | undefined = null; + private devMode: boolean = !electron.app.isPackaged; constructor(app: electron.App, store: any) { this.app = app; diff --git a/src/main/index.ts b/src/main/index.ts index 72391f5b..6862aa18 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -13,7 +13,7 @@ import PluginHandler from "./base/plugins"; const config = new ConfigStore(); const App = new AppEvents(config.store); const Cider = new Win(electron.app, config.store) -const plug = new PluginHandler(); +const plug = new PluginHandler(config.store); let win: Electron.BrowserWindow; @@ -34,7 +34,7 @@ electron.app.on('ready', () => { win = await Cider.createWindow() App.bwCreated(win); /// please dont change this for plugins to get proper and fully initialized Win objects - plug.callPlugins('onReady', Cider); + plug.callPlugins('onReady', win); win.on("ready-to-show", () => { win.show(); }); diff --git a/src/main/plugins/Extras/examplePlugin.ts b/src/main/plugins/Extras/examplePlugin.ts index 78a194a9..e96045fb 100644 --- a/src/main/plugins/Extras/examplePlugin.ts +++ b/src/main/plugins/Extras/examplePlugin.ts @@ -1,10 +1,11 @@ let i = 1, k = 1; export default class ExamplePlugin { - /** - * Private variables for interaction in plugins - */ - private _win: any; - private _app: any; + /** + * Private variables for interaction in plugins + */ + private _win: any; + private _app: any; + private _store: any; /** * Base Plugin Details (Eventually implemented into a GUI in settings) @@ -17,16 +18,17 @@ export default class ExamplePlugin { /** * Runs on plugin load (Currently run on application start) */ - constructor(app: any) { - this._app = app; - console.log('Example plugin loaded'); - } + constructor(app: any, store: any) { + this._app = app; + this._store = store; + console.log('Example plugin loaded'); + } /** * Runs on app ready */ onReady(win: any): void { - this._win = win; + this._win = win; console.log('Example plugin ready'); } @@ -42,7 +44,7 @@ export default class ExamplePlugin { * @param attributes Music Attributes (attributes.state = current state) */ onPlaybackStateDidChange(attributes: object): void { - console.log('onPlaybackStateDidChange has been called ' + i +' times'); + console.log('onPlaybackStateDidChange has been called ' + i + ' times'); i++ } @@ -51,7 +53,7 @@ export default class ExamplePlugin { * @param attributes Music Attributes */ onNowPlayingItemDidChange(attributes: object): void { - console.log('onNowPlayingDidChange has been called ' + k +' times'); + console.log('onNowPlayingDidChange has been called ' + k + ' times'); k++ } diff --git a/src/main/plugins/discordrpc.ts b/src/main/plugins/discordrpc.ts index 77ce5482..43d4b849 100644 --- a/src/main/plugins/discordrpc.ts +++ b/src/main/plugins/discordrpc.ts @@ -1,28 +1,30 @@ -import * as electron from 'electron'; import * as DiscordRPC from 'discord-rpc' + export default class DiscordRPCPlugin { - /** - * Private variables for interaction in plugins - */ - private _win: any; - private _app: any; + /** + * Private variables for interaction in plugins + */ + private _win: Electron.BrowserWindow | undefined; + private _app: any; + private _store: any; private _discord: any; + private connect(clientId: any) { - this._discord = { isConnected: false }; - if (this._win.store.store.general.discord_rpc == 0 || this._discord.isConnected) return; + this._discord = {isConnected: false}; + if (this._store.general.discord_rpc == 0 || this._discord.isConnected) return; DiscordRPC.register(clientId) // Apparently needed for ask to join, join, spectate etc. - const client = new DiscordRPC.Client({ transport: "ipc" }); - this._discord = Object.assign(client, { error: false, activityCache: null, isConnected: false }); + const client = new DiscordRPC.Client({transport: "ipc"}); + this._discord = Object.assign(client, {error: false, activityCache: null, isConnected: false}); // Login to Discord - this._discord.login({ clientId }) + this._discord.login({clientId}) .then(() => { this._discord.isConnected = true; }) - .catch((e : any) => console.error(`[DiscordRPC][connect] ${e}`)); + .catch((e: any) => console.error(`[DiscordRPC][connect] ${e}`)); - this._discord.on('ready', () => { + this._discord.on('ready', () => { console.log(`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${client.user.username} (${client.user.id})`); }) @@ -34,19 +36,19 @@ export default class DiscordRPCPlugin { }); } - /** + /** * Disconnects from Discord RPC */ private disconnect() { - if (this._win.store.store.general.discord_rpc == 0 || !this._discord.isConnected) return; - + if (this._store.general.discord_rpc == 0 || !this._discord.isConnected) return; + try { this._discord.destroy().then(() => { this._discord.isConnected = false; console.log('[DiscordRPC][disconnect] Disconnected from discord.') - }).catch((e : any) => console.error(`[DiscordRPC][disconnect] ${e}`)); + }).catch((e: any) => console.error(`[DiscordRPC][disconnect] ${e}`)); } catch (err) { - console.error(err) + console.error(err) } } @@ -54,54 +56,57 @@ export default class DiscordRPCPlugin { * Sets the activity of the client * @param {object} attributes */ - private updateActivity(attributes : any) { - if (this._win.store.store.general.discord_rpc == 0) return; + private updateActivity(attributes: any) { + if (this._store.general.discord_rpc == 0) return; if (!this._discord.isConnected) { - this._discord.clearActivity().catch((e : any) => console.error(`[DiscordRPC][updateActivity] ${e}`)); + this._discord.clearActivity().catch((e: any) => console.error(`[DiscordRPC][updateActivity] ${e}`)); return; } // console.log('[DiscordRPC][updateActivity] Updating Discord Activity.') const listenURL = `https://cider.sh/p?s&id=${attributes.playParams.id}` // cider://play/s/[id] (for song) - //console.log(attributes) + //console.log(attributes) - interface ActObject { + interface ActObject extends DiscordRPC.Presence { details?: any, state?: any, startTimestamp?: any, endTimestamp?: any, - largeImageKey? : any, + largeImageKey?: any, largeImageText?: any, smallImageKey?: any, smallImageText?: any, instance: true, buttons?: [ - { label: "Listen on Cider", url?: any }, + { + label: string, + url: string + } ] - } + } - let ActivityObject : ActObject | null = { + let ActivityObject: ActObject | null = { details: attributes.name, state: `by ${attributes.artistName}`, startTimestamp: attributes.startTime, endTimestamp: attributes.endTime, - largeImageKey : (attributes.artwork.url.replace('{w}', '1024').replace('{h}', '1024')) ?? 'cider', + largeImageKey: (attributes.artwork.url.replace('{w}', '1024').replace('{h}', '1024')) ?? 'cider', largeImageText: attributes.albumName, smallImageKey: (attributes.status ? 'play' : 'pause'), smallImageText: (attributes.status ? 'Playing' : 'Paused'), instance: true, buttons: [ - { label: "Listen on Cider", url: listenURL }, + {label: "Listen on Cider", url: listenURL}, ] }; if (ActivityObject.largeImageKey == "" || ActivityObject.largeImageKey == null) { - ActivityObject.largeImageKey = (this._win.store.store.general.discord_rpc == 1) ? "cider" : "logo" + ActivityObject.largeImageKey = (this._store.general.discord_rpc == 1) ? "cider" : "logo" } // Remove the pause/play icon and test for clear activity on pause - if (this._win.store.store.general.discordClearActivityOnPause == 1) { + if (this._store.general.discordClearActivityOnPause == 1) { delete ActivityObject.smallImageKey delete ActivityObject.smallImageText } @@ -128,13 +133,11 @@ export default class DiscordRPCPlugin { } - - // Check if its pausing (false) or playing (true) if (!attributes.status) { - if (this._win.store.store.general.discordClearActivityOnPause == 1) { - this._discord.clearActivity().catch((e : any) => console.error(`[DiscordRPC][clearActivity] ${e}`)); - ActivityObject = null + if (this._store.general.discordClearActivityOnPause == 1) { + this._discord.clearActivity().catch((e: any) => console.error(`[DiscordRPC][clearActivity] ${e}`)); + ActivityObject = null } else { delete ActivityObject.startTimestamp delete ActivityObject.endTimestamp @@ -168,16 +171,17 @@ export default class DiscordRPCPlugin { /** * Runs on plugin load (Currently run on application start) */ - constructor(app: any) { - this._app = app; - } + constructor(app: any, store: any) { + this._app = app; + this._store = store + } /** * Runs on app ready */ onReady(win: any): void { - this._win = win; - this.connect((this._win.store.store.general.discord_rpc == 1) ? '911790844204437504' : '886578863147192350'); + this._win = win; + this.connect((this._store.general.discord_rpc == 1) ? '911790844204437504' : '886578863147192350'); // electron.ipcMain.on("forceUpdateRPC", (event, attributes : object) => { // this.updateActivity(attributes) // }); diff --git a/src/main/plugins/lastfm.ts b/src/main/plugins/lastfm.ts index 78fdfa90..d586b857 100644 --- a/src/main/plugins/lastfm.ts +++ b/src/main/plugins/lastfm.ts @@ -1,7 +1,6 @@ import * as electron from 'electron'; import * as fs from 'fs'; import {resolve} from 'path'; -//@ts-ignore export default class LastFMPlugin { private sessionPath = resolve(electron.app.getPath('userData'), 'session.json'); @@ -15,6 +14,7 @@ export default class LastFMPlugin { private _win: any; private _app: any; private _lastfm: any; + private _store: any; private authenticateFromFile() { let sessionData = require(this.sessionPath) @@ -26,12 +26,12 @@ export default class LastFMPlugin { authenticate() { try { - if (this._win.store.store.lastfm.auth_token) { - this._win.store.store.lastfm.enabled = true; + if (this._store.lastfm.auth_token) { + this._store.lastfm.enabled = true; } - if (!this._win.store.store.lastfm.enabled || !this._win.store.store.lastfm.auth_token) { - this._win.store.store.lastfm.enabled = false; + if (!this._store.lastfm.enabled || !this._store.lastfm.auth_token) { + this._store.lastfm.enabled = false; return } /// dont move this require to top , app wont load @@ -47,8 +47,8 @@ export default class LastFMPlugin { if (err) { console.error("[LastFM][Session] Session file couldn't be opened or doesn't exist,", err) console.log("[LastFM][Auth] Beginning authentication from configuration") - console.log("[LastFM][tk]", this._win.store.store.lastfm.auth_token) - this._lastfm.authenticate(this._win.store.store.lastfm.auth_token, (err: any, session: any) => { + console.log("[LastFM][tk]", this._store.lastfm.auth_token) + this._lastfm.authenticate(this._store.lastfm.auth_token, (err: any, session: any) => { if (err) { throw err; } @@ -78,7 +78,7 @@ export default class LastFMPlugin { } private async scrobbleSong(attributes: any) { - await new Promise(resolve => setTimeout(resolve, Math.round(attributes.durationInMillis * (this._win.store.store.lastfm.scrobble_after / 100)))); + await new Promise(resolve => setTimeout(resolve, Math.round(attributes.durationInMillis * (this._store.lastfm.scrobble_after / 100)))); const currentAttributes = attributes; if (!this._lastfm || this._lastfm.cachedAttributes === attributes) { @@ -117,7 +117,7 @@ export default class LastFMPlugin { } private filterArtistName(artist: any) { - if (!this._win.store.store.lastfm.enabledRemoveFeaturingArtists) return artist; + if (!this._store.lastfm.enabledRemoveFeaturingArtists) return artist; artist = artist.split(' '); if (artist.includes('&')) { @@ -135,7 +135,7 @@ export default class LastFMPlugin { } private updateNowPlayingSong(attributes: any) { - if (!this._lastfm || this._lastfm.cachedNowPlayingAttributes === attributes || !this._win.store.store.lastfm.NowPlaying) { + if (!this._lastfm || this._lastfm.cachedNowPlayingAttributes === attributes || !this._store.lastfm.NowPlaying) { return } @@ -177,8 +177,9 @@ export default class LastFMPlugin { /** * Runs on plugin load (Currently run on application start) */ - constructor(app: any) { + constructor(app: any, store: any) { this._app = app; + this._store = store electron.app.on('second-instance', (_e: any, argv: any) => { // Checks if first instance is authorized and if second instance has protocol args argv.forEach((value: any) => { @@ -187,8 +188,8 @@ export default class LastFMPlugin { let authURI = String(argv).split('/auth/')[1]; if (authURI.startsWith('lastfm')) { // If we wanted more auth options const authKey = authURI.split('lastfm?token=')[1]; - this._win.store.store.lastfm.enabled = true; - this._win.store.store.lastfm.auth_token = authKey; + this._store.lastfm.enabled = true; + this._store.lastfm.auth_token = authKey; console.log(authKey); this._win.win.webContents.send('LastfmAuthenticated', authKey); this.authenticate(); @@ -203,8 +204,8 @@ export default class LastFMPlugin { let authURI = String(arg).split('/auth/')[1]; if (authURI.startsWith('lastfm')) { // If we wanted more auth options const authKey = authURI.split('lastfm?token=')[1]; - this._win.store.store.lastfm.enabled = true; - this._win.store.store.lastfm.auth_token = authKey; + this._store.lastfm.enabled = true; + this._store.lastfm.auth_token = authKey; this._win.win.webContents.send('LastfmAuthenticated', authKey); console.log(authKey); this.authenticate(); diff --git a/src/main/plugins/mpris.ts b/src/main/plugins/mpris.ts new file mode 100644 index 00000000..03cdb544 --- /dev/null +++ b/src/main/plugins/mpris.ts @@ -0,0 +1,196 @@ +// @ts-ignore +import * as Player from 'mpris-service'; + +export default class MPRIS { + /** + * Private variables for interaction in plugins + */ + private _win: any; + private _app: any; + + /** + * Base Plugin Details (Eventually implemented into a GUI in settings) + */ + public name: string = 'MPRIS Service'; + public description: string = 'Handles MPRIS service calls for Linux systems.'; + public version: string = '1.0.0'; + public author: string = 'Core'; + + /** + * MPRIS Service + */ + private mpris: any; + private mprisEvents: Object = { + "playpause": "pausePlay", + "play": "pausePlay", + "pause": "pausePlay", + "next": "nextTrack", + "previous": "previousTrack", + } + + /******************************************************************************************* + * Private Methods + * ****************************************************************************************/ + + /** + * Runs a media event + * @param type - pausePlay, nextTrack, PreviousTrack + * @private + */ + private runMediaEvent(type: string) { + if (this._win) { + this._win.webContents.executeJavaScript(`MusicKitInterop.${type}()`).catch(console.error) + } + } + + /** + * Blocks non-linux systems from running this plugin + * @private + */ + private static linuxOnly(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) { + if (process.platform !== 'linux') { + descriptor.value = function () { + return + } + } + + } + + + /** + * Connects to MPRIS Service + */ + @MPRIS.linuxOnly + private connect() { + this.mpris = Player({ + name: 'Cider', + identity: 'Cider', + supportedUriSchemes: [], + supportedMimeTypes: [], + supportedInterfaces: ['player'] + }); + this.mpris = Object.assign(this.mpris, { + canQuit: true, + canControl: true, + canPause: true, + canPlay: true, + canGoNext: true, + active: true + }) + + + const pos_atr = {durationInMillis: 0}; + this.mpris.getPosition = function () { + const durationInMicro = pos_atr.durationInMillis * 1000; + const percentage = parseFloat("0") || 0; + return durationInMicro * percentage; + } + + for (const [key, value] of Object.entries(this.mprisEvents)) { + this.mpris.on(key, () => { + this.runMediaEvent(value) + }); + } + } + + /** + * Update MPRIS Player Attributes + */ + @MPRIS.linuxOnly + private updatePlayer(attributes: any) { + + const MetaData = { + 'mpris:trackid': this.mpris.objectPath(`track/${attributes.playParams.id.replace(/[.]+/g, "")}`), + 'mpris:length': attributes.durationInMillis * 1000, // In microseconds + 'mpris:artUrl': (attributes.artwork.url.replace('/{w}x{h}bb', '/512x512bb')).replace('/2000x2000bb', '/35x35bb'), + 'xesam:title': `${attributes.name}`, + 'xesam:album': `${attributes.albumName}`, + 'xesam:artist': [`${attributes.artistName}`,], + 'xesam:genre': attributes.genreNames + } + + if (this.mpris.metadata["mpris:trackid"] === MetaData["mpris:trackid"]) { + return + } + + this.mpris.metadata = MetaData + + } + + /** + * Update MPRIS Player State + * @private + * @param attributes + */ + @MPRIS.linuxOnly + private updatePlayerState(attributes: any) { + + let status = 'Stopped'; + if (attributes.status) { + status = 'Playing'; + } else if (attributes.status === false) { + status = 'Paused'; + } + + if (this.mpris.playbackStatus === status) { + return + } + this.mpris.playbackStatus = status; + } + + /** + * Clear state + * @private + */ + private clearState() { + this.mpris.metadata = {'mpris:trackid': '/org/mpris/MediaPlayer2/TrackList/NoTrack'} + this.mpris.playbackStatus = 'Stopped'; + } + + + /******************************************************************************************* + * Public Methods + * ****************************************************************************************/ + + /** + * Runs on plugin load (Currently run on application start) + */ + constructor(app: any, _store: any) { + this._app = app; + console.log(`[${this.name}] plugin loaded`); + } + + /** + * Runs on app ready + */ + onReady(win: any): void { + this._win = win; + console.log(`[${this.name}] plugin ready`); + this.connect() + } + + /** + * Runs on app stop + */ + onBeforeQuit(): void { + console.log(`[${this.name}] plugin stopped`); + this.clearState() + } + + /** + * Runs on playback State Change + * @param attributes Music Attributes (attributes.state = current state) + */ + onPlaybackStateDidChange(attributes: object): void { + this.updatePlayerState(attributes) + } + + /** + * Runs on song change + * @param attributes Music Attributes + */ + onNowPlayingItemDidChange(attributes: object): void { + this.updatePlayer(attributes); + } + +} diff --git a/src/renderer/audio/audio.js b/src/renderer/audio/audio.js index 64118b7c..6697d908 100644 --- a/src/renderer/audio/audio.js +++ b/src/renderer/audio/audio.js @@ -5,6 +5,7 @@ var CiderAudio = { gainNode : null, spatialNode : null, spatialInput: null, + audioBands : null, }, init: function (cb = function () { }) { //AudioOutputs.fInit = true; @@ -42,6 +43,7 @@ var CiderAudio = { if (app.cfg.audio.spatial){ CiderAudio.spatialOn() } + CiderAudio.equalizer() }, normalizerOn: function (){}, normalizerOff: function (){ @@ -49,7 +51,7 @@ var CiderAudio = { }, spatialOn: function (){ try{ - CiderAudio.audioNodes.gainNode.connect(CiderAudio.context.destination);} catch(e){} + CiderAudio.audioNodes.gainNode.disconnect(CiderAudio.context.destination);} catch(e){} CiderAudio.audioNodes.spatialNode = new ResonanceAudio(CiderAudio.context); CiderAudio.audioNodes.spatialNode.output.connect(CiderAudio.context.destination); let roomDimensions = { @@ -90,6 +92,43 @@ var CiderAudio = { } ); } + }, + equalizer: function (){ + const BANDS = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000]; + CiderAudio.audioNodes.audioBands = {}; + + BANDS.forEach((band, i) => { + const filter = CiderAudio.context.createBiquadFilter(); + + CiderAudio.audioNodes.audioBands[band] = filter; + + if (i === 0) { + // The first filter, includes all lower frequencies + filter.type = "lowshelf"; + } else if (i === BANDS.length - 1) { + // The last filter, includes all higher frequencies + filter.type = "highshelf"; + } else { + filter.type = "peaking"; + } + filter.frequency.value = band; + filter.gain.value = 0; + if (i == 0){ + if (app.cfg.audio.spatial) { + CiderAudio.audioNodes.spatialNode.output.disconnect(CiderAudio.context.destination); + CiderAudio.audioNodes.spatialNode.output.connect(filter); + } else { + CiderAudio.audioNodes.gainNode.disconnect(CiderAudio.context.destination); + CiderAudio.audioNodes.gainNode.connect(filter); + } + } else if (i === BANDS.length - 1) { + CiderAudio.audioNodes.audioBands[BANDS[i - 1]].connect(filter); + } else { + CiderAudio.audioNodes.audioBands[BANDS[i - 1]].connect(filter); + filter.connect(CiderAudio.context.destination); + } + + }); } } diff --git a/src/renderer/index.js b/src/renderer/index.js index 1c470073..13168409 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -297,7 +297,8 @@ const app = new Vue({ modals: { addToPlaylist: false, spatialProperties: false, - qrcode: false + qrcode: false, + equalizer: false, }, socialBadges: { badgeMap: {}, diff --git a/src/renderer/style.less b/src/renderer/style.less index 7727e5f2..41e28dbb 100644 --- a/src/renderer/style.less +++ b/src/renderer/style.less @@ -5701,4 +5701,72 @@ body.no-gpu { overflow-y: hidden; } +.equalizer-panel { + .modal-window { + height: 300px; + max-height: 300px; + width: 400px; + max-width: 400px; + overflow: hidden; + + .info-header { + padding-left: 12px; + } + + .visual-container { + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + } + + + .modal-header { + padding: 16px; + position: relative; + overflow: hidden; + + .modal-title { + text-align: center; + } + + .close-btn { + width: 50px; + height: 100%; + background-image: var(--gfx-closeBtn); + background-position: center; + background-repeat: no-repeat; + -webkit-app-region: no-drag; + appearance: none; + border: 0; + background-color: transparent; + position: absolute; + top: 0; + right: 0; + + &:hover { + background-color: rgb(196, 43, 28) + } + } + } + .modal-content{ + display: block; + .input-container{ + display: inline-grid; + width: 35px; + justify-items: center; + font-size: 0.7em; + } + .reset-button{ + width: 50%; + margin-left: 25%; + } + } + } +} +input.eq-slider { + -webkit-appearance: slider-vertical; + width: 5%; +} + @import url("less/compact.less"); diff --git a/src/renderer/views/components/equalizer.ejs b/src/renderer/views/components/equalizer.ejs new file mode 100644 index 00000000..5ae27962 --- /dev/null +++ b/src/renderer/views/components/equalizer.ejs @@ -0,0 +1,108 @@ + + + \ No newline at end of file diff --git a/src/renderer/views/main.ejs b/src/renderer/views/main.ejs index 4b9e8a30..5b2f56dd 100644 --- a/src/renderer/views/main.ejs +++ b/src/renderer/views/main.ejs @@ -207,7 +207,7 @@
- Apple Music + {{$root.getLz('term.appleMusic')}}
@@ -216,7 +216,7 @@
- Library + {{$root.getLz('term.library')}}
@@ -264,6 +264,10 @@ +