Merge branch 'upcoming' of https://github.com/ciderapp/Cider into upcoming
This commit is contained in:
commit
694a75c4a0
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": {
|
||||||
|
@ -89,14 +89,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