Various updates to backend.
Implementation of MPRIS in TS. LastFM now interacts with store object passed directly into class. Experimental decorators enabled and utilised in MPRIS.
This commit is contained in:
parent
b4293cf065
commit
27becacbb7
10 changed files with 305 additions and 84 deletions
|
@ -50,7 +50,7 @@ export class AppEvents {
|
||||||
/***********************************************************************************************************************
|
/***********************************************************************************************************************
|
||||||
* Commandline arguments
|
* Commandline arguments
|
||||||
**********************************************************************************************************************/
|
**********************************************************************************************************************/
|
||||||
switch (store.get("visual.hw_acceleration")) {
|
switch (store.visual.hw_acceleration) {
|
||||||
default:
|
default:
|
||||||
case "default":
|
case "default":
|
||||||
electron.app.commandLine.appendSwitch('enable-accelerated-mjpeg-decode')
|
electron.app.commandLine.appendSwitch('enable-accelerated-mjpeg-decode')
|
||||||
|
@ -75,6 +75,10 @@ export class AppEvents {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.platform === "linux") {
|
||||||
|
electron.app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
|
||||||
|
}
|
||||||
|
|
||||||
/***********************************************************************************************************************
|
/***********************************************************************************************************************
|
||||||
* Protocols
|
* Protocols
|
||||||
**********************************************************************************************************************/
|
**********************************************************************************************************************/
|
||||||
|
|
|
@ -5,10 +5,11 @@ import * as electron from 'electron'
|
||||||
export default class PluginHandler {
|
export default class PluginHandler {
|
||||||
private basePluginsPath = path.join(__dirname, '../plugins');
|
private basePluginsPath = path.join(__dirname, '../plugins');
|
||||||
private userPluginsPath = path.join(electron.app.getPath('userData'), 'plugins');
|
private userPluginsPath = path.join(electron.app.getPath('userData'), 'plugins');
|
||||||
private pluginsList: any = {};
|
private readonly pluginsList: any = {};
|
||||||
|
private readonly _store: any;
|
||||||
constructor() {
|
|
||||||
|
|
||||||
|
constructor(config: any) {
|
||||||
|
this._store = config;
|
||||||
this.pluginsList = this.getPlugins();
|
this.pluginsList = this.getPlugins();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ export default class PluginHandler {
|
||||||
if (plugins[file] || plugin.name in plugins) {
|
if (plugins[file] || plugin.name in plugins) {
|
||||||
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||||
} else {
|
} 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) {
|
if (plugins[file] || plugin in plugins) {
|
||||||
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||||
} else {
|
} else {
|
||||||
plugins[file] = new plugin(electron.app);
|
plugins[file] = new plugin(electron.app, this._store);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as Store from 'electron-store';
|
||||||
import * as electron from "electron";
|
import * as electron from "electron";
|
||||||
|
|
||||||
export class ConfigStore {
|
export class ConfigStore {
|
||||||
public store: Store | undefined;
|
private _store: Store;
|
||||||
|
|
||||||
private defaults: any = {
|
private defaults: any = {
|
||||||
"general": {
|
"general": {
|
||||||
|
@ -96,14 +96,26 @@ export class ConfigStore {
|
||||||
private migrations: any = {}
|
private migrations: any = {}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.store = new Store({
|
this._store = new Store({
|
||||||
name: 'cider-config',
|
name: 'cider-config',
|
||||||
defaults: this.defaults,
|
defaults: this.defaults,
|
||||||
migrations: this.migrations,
|
migrations: this.migrations,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.store.set(this.mergeStore(this.defaults, this.store.store))
|
this._store.set(this.mergeStore(this.defaults, this._store.store))
|
||||||
this.ipcHandler(this.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,10 +15,10 @@ import {wsapi} from "./wsapi";
|
||||||
import * as jsonc from "jsonc";
|
import * as jsonc from "jsonc";
|
||||||
|
|
||||||
export class Win {
|
export class Win {
|
||||||
win: any | undefined = null;
|
private win: any | undefined = null;
|
||||||
app: any | undefined = null;
|
private app: any | undefined = null;
|
||||||
store: any | undefined = null;
|
private store: any | undefined = null;
|
||||||
devMode: boolean = !electron.app.isPackaged;
|
private devMode: boolean = !electron.app.isPackaged;
|
||||||
|
|
||||||
constructor(app: electron.App, store: any) {
|
constructor(app: electron.App, store: any) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
|
|
|
@ -13,7 +13,7 @@ import PluginHandler from "./base/plugins";
|
||||||
const config = new ConfigStore();
|
const config = new ConfigStore();
|
||||||
const App = new AppEvents(config.store);
|
const App = new AppEvents(config.store);
|
||||||
const Cider = new Win(electron.app, config.store)
|
const Cider = new Win(electron.app, config.store)
|
||||||
const plug = new PluginHandler();
|
const plug = new PluginHandler(config.store);
|
||||||
|
|
||||||
let win: Electron.BrowserWindow;
|
let win: Electron.BrowserWindow;
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ electron.app.on('ready', () => {
|
||||||
win = await Cider.createWindow()
|
win = await Cider.createWindow()
|
||||||
App.bwCreated(win);
|
App.bwCreated(win);
|
||||||
/// please dont change this for plugins to get proper and fully initialized Win objects
|
/// 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.on("ready-to-show", () => {
|
||||||
win.show();
|
win.show();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
let i = 1, k = 1;
|
let i = 1, k = 1;
|
||||||
export default class ExamplePlugin {
|
export default class ExamplePlugin {
|
||||||
/**
|
/**
|
||||||
* Private variables for interaction in plugins
|
* Private variables for interaction in plugins
|
||||||
*/
|
*/
|
||||||
private _win: any;
|
private _win: any;
|
||||||
private _app: any;
|
private _app: any;
|
||||||
|
private _store: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
* 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)
|
* Runs on plugin load (Currently run on application start)
|
||||||
*/
|
*/
|
||||||
constructor(app: any) {
|
constructor(app: any, store: any) {
|
||||||
this._app = app;
|
this._app = app;
|
||||||
console.log('Example plugin loaded');
|
this._store = store;
|
||||||
}
|
console.log('Example plugin loaded');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs on app ready
|
* Runs on app ready
|
||||||
*/
|
*/
|
||||||
onReady(win: any): void {
|
onReady(win: any): void {
|
||||||
this._win = win;
|
this._win = win;
|
||||||
console.log('Example plugin ready');
|
console.log('Example plugin ready');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +44,7 @@ export default class ExamplePlugin {
|
||||||
* @param attributes Music Attributes (attributes.state = current state)
|
* @param attributes Music Attributes (attributes.state = current state)
|
||||||
*/
|
*/
|
||||||
onPlaybackStateDidChange(attributes: object): void {
|
onPlaybackStateDidChange(attributes: object): void {
|
||||||
console.log('onPlaybackStateDidChange has been called ' + i +' times');
|
console.log('onPlaybackStateDidChange has been called ' + i + ' times');
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +53,7 @@ export default class ExamplePlugin {
|
||||||
* @param attributes Music Attributes
|
* @param attributes Music Attributes
|
||||||
*/
|
*/
|
||||||
onNowPlayingItemDidChange(attributes: object): void {
|
onNowPlayingItemDidChange(attributes: object): void {
|
||||||
console.log('onNowPlayingDidChange has been called ' + k +' times');
|
console.log('onNowPlayingDidChange has been called ' + k + ' times');
|
||||||
k++
|
k++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,30 @@
|
||||||
import * as electron from 'electron';
|
|
||||||
import * as DiscordRPC from 'discord-rpc'
|
import * as DiscordRPC from 'discord-rpc'
|
||||||
|
|
||||||
export default class DiscordRPCPlugin {
|
export default class DiscordRPCPlugin {
|
||||||
/**
|
/**
|
||||||
* Private variables for interaction in plugins
|
* Private variables for interaction in plugins
|
||||||
*/
|
*/
|
||||||
private _win: any;
|
private _win: Electron.BrowserWindow | undefined;
|
||||||
private _app: any;
|
private _app: any;
|
||||||
|
private _store: any;
|
||||||
private _discord: any;
|
private _discord: any;
|
||||||
|
|
||||||
private connect(clientId: any) {
|
private connect(clientId: any) {
|
||||||
this._discord = { isConnected: false };
|
this._discord = {isConnected: false};
|
||||||
if (this._win.store.store.general.discord_rpc == 0 || this._discord.isConnected) return;
|
if (this._store.general.discord_rpc == 0 || this._discord.isConnected) return;
|
||||||
|
|
||||||
DiscordRPC.register(clientId) // Apparently needed for ask to join, join, spectate etc.
|
DiscordRPC.register(clientId) // Apparently needed for ask to join, join, spectate etc.
|
||||||
const client = new DiscordRPC.Client({ transport: "ipc" });
|
const client = new DiscordRPC.Client({transport: "ipc"});
|
||||||
this._discord = Object.assign(client, { error: false, activityCache: null, isConnected: false });
|
this._discord = Object.assign(client, {error: false, activityCache: null, isConnected: false});
|
||||||
|
|
||||||
// Login to Discord
|
// Login to Discord
|
||||||
this._discord.login({ clientId })
|
this._discord.login({clientId})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this._discord.isConnected = true;
|
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})`);
|
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
|
* Disconnects from Discord RPC
|
||||||
*/
|
*/
|
||||||
private disconnect() {
|
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 {
|
try {
|
||||||
this._discord.destroy().then(() => {
|
this._discord.destroy().then(() => {
|
||||||
this._discord.isConnected = false;
|
this._discord.isConnected = false;
|
||||||
console.log('[DiscordRPC][disconnect] Disconnected from discord.')
|
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) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,54 +56,57 @@ export default class DiscordRPCPlugin {
|
||||||
* Sets the activity of the client
|
* Sets the activity of the client
|
||||||
* @param {object} attributes
|
* @param {object} attributes
|
||||||
*/
|
*/
|
||||||
private updateActivity(attributes : any) {
|
private updateActivity(attributes: any) {
|
||||||
if (this._win.store.store.general.discord_rpc == 0) return;
|
if (this._store.general.discord_rpc == 0) return;
|
||||||
|
|
||||||
if (!this._discord.isConnected) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('[DiscordRPC][updateActivity] Updating Discord Activity.')
|
// console.log('[DiscordRPC][updateActivity] Updating Discord Activity.')
|
||||||
|
|
||||||
const listenURL = `https://cider.sh/p?s&id=${attributes.playParams.id}` // cider://play/s/[id] (for song)
|
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,
|
details?: any,
|
||||||
state?: any,
|
state?: any,
|
||||||
startTimestamp?: any,
|
startTimestamp?: any,
|
||||||
endTimestamp?: any,
|
endTimestamp?: any,
|
||||||
largeImageKey? : any,
|
largeImageKey?: any,
|
||||||
largeImageText?: any,
|
largeImageText?: any,
|
||||||
smallImageKey?: any,
|
smallImageKey?: any,
|
||||||
smallImageText?: any,
|
smallImageText?: any,
|
||||||
instance: true,
|
instance: true,
|
||||||
buttons?: [
|
buttons?: [
|
||||||
{ label: "Listen on Cider", url?: any },
|
{
|
||||||
|
label: string,
|
||||||
|
url: string
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
let ActivityObject : ActObject | null = {
|
let ActivityObject: ActObject | null = {
|
||||||
details: attributes.name,
|
details: attributes.name,
|
||||||
state: `by ${attributes.artistName}`,
|
state: `by ${attributes.artistName}`,
|
||||||
startTimestamp: attributes.startTime,
|
startTimestamp: attributes.startTime,
|
||||||
endTimestamp: attributes.endTime,
|
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,
|
largeImageText: attributes.albumName,
|
||||||
smallImageKey: (attributes.status ? 'play' : 'pause'),
|
smallImageKey: (attributes.status ? 'play' : 'pause'),
|
||||||
smallImageText: (attributes.status ? 'Playing' : 'Paused'),
|
smallImageText: (attributes.status ? 'Playing' : 'Paused'),
|
||||||
instance: true,
|
instance: true,
|
||||||
buttons: [
|
buttons: [
|
||||||
{ label: "Listen on Cider", url: listenURL },
|
{label: "Listen on Cider", url: listenURL},
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
if (ActivityObject.largeImageKey == "" || ActivityObject.largeImageKey == null) {
|
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
|
// 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.smallImageKey
|
||||||
delete ActivityObject.smallImageText
|
delete ActivityObject.smallImageText
|
||||||
}
|
}
|
||||||
|
@ -128,13 +133,11 @@ export default class DiscordRPCPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Check if its pausing (false) or playing (true)
|
// Check if its pausing (false) or playing (true)
|
||||||
if (!attributes.status) {
|
if (!attributes.status) {
|
||||||
if (this._win.store.store.general.discordClearActivityOnPause == 1) {
|
if (this._store.general.discordClearActivityOnPause == 1) {
|
||||||
this._discord.clearActivity().catch((e : any) => console.error(`[DiscordRPC][clearActivity] ${e}`));
|
this._discord.clearActivity().catch((e: any) => console.error(`[DiscordRPC][clearActivity] ${e}`));
|
||||||
ActivityObject = null
|
ActivityObject = null
|
||||||
} else {
|
} else {
|
||||||
delete ActivityObject.startTimestamp
|
delete ActivityObject.startTimestamp
|
||||||
delete ActivityObject.endTimestamp
|
delete ActivityObject.endTimestamp
|
||||||
|
@ -168,16 +171,17 @@ export default class DiscordRPCPlugin {
|
||||||
/**
|
/**
|
||||||
* Runs on plugin load (Currently run on application start)
|
* Runs on plugin load (Currently run on application start)
|
||||||
*/
|
*/
|
||||||
constructor(app: any) {
|
constructor(app: any, store: any) {
|
||||||
this._app = app;
|
this._app = app;
|
||||||
}
|
this._store = store
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs on app ready
|
* Runs on app ready
|
||||||
*/
|
*/
|
||||||
onReady(win: any): void {
|
onReady(win: any): void {
|
||||||
this._win = win;
|
this._win = win;
|
||||||
this.connect((this._win.store.store.general.discord_rpc == 1) ? '911790844204437504' : '886578863147192350');
|
this.connect((this._store.general.discord_rpc == 1) ? '911790844204437504' : '886578863147192350');
|
||||||
// electron.ipcMain.on("forceUpdateRPC", (event, attributes : object) => {
|
// electron.ipcMain.on("forceUpdateRPC", (event, attributes : object) => {
|
||||||
// this.updateActivity(attributes)
|
// this.updateActivity(attributes)
|
||||||
// });
|
// });
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import * as electron from 'electron';
|
import * as electron from 'electron';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import {resolve} from 'path';
|
import {resolve} from 'path';
|
||||||
//@ts-ignore
|
|
||||||
|
|
||||||
export default class LastFMPlugin {
|
export default class LastFMPlugin {
|
||||||
private sessionPath = resolve(electron.app.getPath('userData'), 'session.json');
|
private sessionPath = resolve(electron.app.getPath('userData'), 'session.json');
|
||||||
|
@ -15,6 +14,7 @@ export default class LastFMPlugin {
|
||||||
private _win: any;
|
private _win: any;
|
||||||
private _app: any;
|
private _app: any;
|
||||||
private _lastfm: any;
|
private _lastfm: any;
|
||||||
|
private _store: any;
|
||||||
|
|
||||||
private authenticateFromFile() {
|
private authenticateFromFile() {
|
||||||
let sessionData = require(this.sessionPath)
|
let sessionData = require(this.sessionPath)
|
||||||
|
@ -26,12 +26,12 @@ export default class LastFMPlugin {
|
||||||
|
|
||||||
authenticate() {
|
authenticate() {
|
||||||
try {
|
try {
|
||||||
if (this._win.store.store.lastfm.auth_token) {
|
if (this._store.lastfm.auth_token) {
|
||||||
this._win.store.store.lastfm.enabled = true;
|
this._store.lastfm.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._win.store.store.lastfm.enabled || !this._win.store.store.lastfm.auth_token) {
|
if (!this._store.lastfm.enabled || !this._store.lastfm.auth_token) {
|
||||||
this._win.store.store.lastfm.enabled = false;
|
this._store.lastfm.enabled = false;
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
/// dont move this require to top , app wont load
|
/// dont move this require to top , app wont load
|
||||||
|
@ -47,8 +47,8 @@ export default class LastFMPlugin {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("[LastFM][Session] Session file couldn't be opened or doesn't exist,", 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][Auth] Beginning authentication from configuration")
|
||||||
console.log("[LastFM][tk]", this._win.store.store.lastfm.auth_token)
|
console.log("[LastFM][tk]", this._store.lastfm.auth_token)
|
||||||
this._lastfm.authenticate(this._win.store.store.lastfm.auth_token, (err: any, session: any) => {
|
this._lastfm.authenticate(this._store.lastfm.auth_token, (err: any, session: any) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ export default class LastFMPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async scrobbleSong(attributes: any) {
|
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;
|
const currentAttributes = attributes;
|
||||||
|
|
||||||
if (!this._lastfm || this._lastfm.cachedAttributes === attributes) {
|
if (!this._lastfm || this._lastfm.cachedAttributes === attributes) {
|
||||||
|
@ -117,7 +117,7 @@ export default class LastFMPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
private filterArtistName(artist: any) {
|
private filterArtistName(artist: any) {
|
||||||
if (!this._win.store.store.lastfm.enabledRemoveFeaturingArtists) return artist;
|
if (!this._store.lastfm.enabledRemoveFeaturingArtists) return artist;
|
||||||
|
|
||||||
artist = artist.split(' ');
|
artist = artist.split(' ');
|
||||||
if (artist.includes('&')) {
|
if (artist.includes('&')) {
|
||||||
|
@ -135,7 +135,7 @@ export default class LastFMPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateNowPlayingSong(attributes: any) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,8 +177,9 @@ export default class LastFMPlugin {
|
||||||
/**
|
/**
|
||||||
* Runs on plugin load (Currently run on application start)
|
* Runs on plugin load (Currently run on application start)
|
||||||
*/
|
*/
|
||||||
constructor(app: any) {
|
constructor(app: any, store: any) {
|
||||||
this._app = app;
|
this._app = app;
|
||||||
|
this._store = store
|
||||||
electron.app.on('second-instance', (_e: any, argv: any) => {
|
electron.app.on('second-instance', (_e: any, argv: any) => {
|
||||||
// Checks if first instance is authorized and if second instance has protocol args
|
// Checks if first instance is authorized and if second instance has protocol args
|
||||||
argv.forEach((value: any) => {
|
argv.forEach((value: any) => {
|
||||||
|
@ -187,8 +188,8 @@ export default class LastFMPlugin {
|
||||||
let authURI = String(argv).split('/auth/')[1];
|
let authURI = String(argv).split('/auth/')[1];
|
||||||
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
|
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
|
||||||
const authKey = authURI.split('lastfm?token=')[1];
|
const authKey = authURI.split('lastfm?token=')[1];
|
||||||
this._win.store.store.lastfm.enabled = true;
|
this._store.lastfm.enabled = true;
|
||||||
this._win.store.store.lastfm.auth_token = authKey;
|
this._store.lastfm.auth_token = authKey;
|
||||||
console.log(authKey);
|
console.log(authKey);
|
||||||
this._win.win.webContents.send('LastfmAuthenticated', authKey);
|
this._win.win.webContents.send('LastfmAuthenticated', authKey);
|
||||||
this.authenticate();
|
this.authenticate();
|
||||||
|
@ -203,8 +204,8 @@ export default class LastFMPlugin {
|
||||||
let authURI = String(arg).split('/auth/')[1];
|
let authURI = String(arg).split('/auth/')[1];
|
||||||
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
|
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
|
||||||
const authKey = authURI.split('lastfm?token=')[1];
|
const authKey = authURI.split('lastfm?token=')[1];
|
||||||
this._win.store.store.lastfm.enabled = true;
|
this._store.lastfm.enabled = true;
|
||||||
this._win.store.store.lastfm.auth_token = authKey;
|
this._store.lastfm.auth_token = authKey;
|
||||||
this._win.win.webContents.send('LastfmAuthenticated', authKey);
|
this._win.win.webContents.send('LastfmAuthenticated', authKey);
|
||||||
console.log(authKey);
|
console.log(authKey);
|
||||||
this.authenticate();
|
this.authenticate();
|
||||||
|
|
196
src/main/plugins/mpris.ts
Normal file
196
src/main/plugins/mpris.ts
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue