CHONKY BOY
This commit is contained in:
parent
31ed921a1a
commit
c15f55d0ee
213 changed files with 64188 additions and 55736 deletions
|
@ -1,372 +1,352 @@
|
|||
import * as electron from 'electron';
|
||||
import * as os from 'os';
|
||||
import {resolve} from 'path';
|
||||
import * as CiderReceiver from '../base/castreceiver';
|
||||
import * as electron from "electron";
|
||||
import * as os from "os";
|
||||
import { resolve } from "path";
|
||||
import * as CiderReceiver from "../base/castreceiver";
|
||||
|
||||
export default class ChromecastPlugin {
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private _win: any;
|
||||
private _app: any;
|
||||
private _lastfm: any;
|
||||
private _store: any;
|
||||
private _timer: any;
|
||||
private audioClient = require("castv2-client").Client;
|
||||
private mdns = require("mdns-js");
|
||||
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private _win: any;
|
||||
private _app: any;
|
||||
private _lastfm: any;
|
||||
private _store: any;
|
||||
private _timer: any;
|
||||
private audioClient = require('castv2-client').Client;
|
||||
private mdns = require('mdns-js');
|
||||
private devices: any = [];
|
||||
private castDevices: any = [];
|
||||
|
||||
private devices: any = [];
|
||||
private castDevices: any = [];
|
||||
// private GCRunning = false;
|
||||
// private GCBuffer: any;
|
||||
// private expectedConnections = 0;
|
||||
// private currentConnections = 0;
|
||||
private activeConnections: any = [];
|
||||
// private requests = [];
|
||||
// private GCstream = new Stream.PassThrough(),
|
||||
private connectedHosts: any = {};
|
||||
private connectedPlayer: any;
|
||||
private ciderPort: any = 9000;
|
||||
// private server = false;
|
||||
// private bufcount = 0;
|
||||
// private bufcount2 = 0;
|
||||
// private headerSent = false;
|
||||
|
||||
// private GCRunning = false;
|
||||
// private GCBuffer: any;
|
||||
// private expectedConnections = 0;
|
||||
// private currentConnections = 0;
|
||||
private activeConnections: any = [];
|
||||
// private requests = [];
|
||||
// private GCstream = new Stream.PassThrough(),
|
||||
private connectedHosts: any = {};
|
||||
private connectedPlayer: any;
|
||||
private ciderPort :any = 9000;
|
||||
// private server = false;
|
||||
// private bufcount = 0;
|
||||
// private bufcount2 = 0;
|
||||
// private headerSent = false;
|
||||
private searchForGCDevices() {
|
||||
try {
|
||||
let browser = this.mdns.createBrowser(this.mdns.tcp("googlecast"));
|
||||
browser.on("ready", browser.discover);
|
||||
|
||||
browser.on("update", (service: any) => {
|
||||
if (service.addresses && service.fullname && service.fullname.includes("_googlecast._tcp")) {
|
||||
let a = service.txt.filter((u: any) => String(u).startsWith("fn="));
|
||||
let name = (a[0] ?? "").substring(3) != "" ? (a[0] ?? "").substring(3) : service.fullname.substring(0, service.fullname.indexOf("._googlecast"));
|
||||
this.ondeviceup(service.addresses[0], name + " (" + (service.type[0]?.description ?? "") + ")", "", "googlecast");
|
||||
}
|
||||
});
|
||||
const Client = require("node-ssdp").Client;
|
||||
// also do a SSDP/UPnP search
|
||||
let ssdpBrowser = new Client();
|
||||
ssdpBrowser.on("response", (headers: any, statusCode: any, rinfo: any) => {
|
||||
var location = getLocation(headers);
|
||||
if (location != null) {
|
||||
this.getServiceDescription(location, rinfo.address);
|
||||
}
|
||||
});
|
||||
|
||||
private searchForGCDevices() {
|
||||
function getLocation(headers: any) {
|
||||
let location = null;
|
||||
if (headers["LOCATION"] != null) {
|
||||
location = headers["LOCATION"];
|
||||
} else if (headers["Location"] != null) {
|
||||
location = headers["Location"];
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
ssdpBrowser.search("urn:dial-multiscreen-org:device:dial:1");
|
||||
|
||||
// // actual upnp devices
|
||||
// if (app.cfg.get("audio.enableDLNA")) {
|
||||
// let ssdpBrowser2 = new Client();
|
||||
// ssdpBrowser2.on('response', (headers, statusCode, rinfo) => {
|
||||
// var location = getLocation(headers);
|
||||
// if (location != null) {
|
||||
// this.getServiceDescription(location, rinfo.address);
|
||||
// }
|
||||
|
||||
// });
|
||||
// ssdpBrowser2.search('urn:schemas-upnp-org:device:MediaRenderer:1');
|
||||
|
||||
// }
|
||||
} catch (e) {
|
||||
console.log("Search GC err", e);
|
||||
}
|
||||
}
|
||||
|
||||
private getServiceDescription(url: any, address: any) {
|
||||
const request = require("request");
|
||||
request.get(url, (error: any, response: any, body: any) => {
|
||||
if (!error && response.statusCode === 200) {
|
||||
this.parseServiceDescription(body, address, url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ondeviceup(host: any, name: any, location: any, type: any) {
|
||||
if (this.castDevices.findIndex((item: any) => item.host === host && item.name === name && item.location === location && item.type === type) === -1) {
|
||||
this.castDevices.push({
|
||||
name: name,
|
||||
host: host,
|
||||
location: location,
|
||||
type: type,
|
||||
});
|
||||
if (this.devices.indexOf(host) === -1) {
|
||||
this.devices.push(host);
|
||||
}
|
||||
if (name) {
|
||||
this._win.webContents.executeJavaScript(`console.log('deviceFound','ip: ${host} name:${name}')`).catch((err: any) => console.error(err));
|
||||
console.log("deviceFound", host, name);
|
||||
}
|
||||
} else {
|
||||
this._win.webContents.executeJavaScript(`console.log('deviceFound (added)','ip: ${host} name:${name}')`).catch((err: any) => console.error(err));
|
||||
console.log("deviceFound (added)", host, name);
|
||||
}
|
||||
}
|
||||
|
||||
private parseServiceDescription(body: any, address: any, url: any) {
|
||||
const parseString = require("xml2js").parseString;
|
||||
parseString(body, (err: any, result: any) => {
|
||||
if (!err && result && result.root && result.root.device) {
|
||||
const device = result.root.device[0];
|
||||
console.log("device", device);
|
||||
let devicetype = "googlecast";
|
||||
console.log();
|
||||
if (device.deviceType && device.deviceType.toString() === "urn:schemas-upnp-org:device:MediaRenderer:1") {
|
||||
devicetype = "upnp";
|
||||
}
|
||||
this.ondeviceup(address, device.friendlyName.toString(), url, devicetype);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private loadMedia(client: any, song: any, artist: any, album: any, albumart: any, cb?: any) {
|
||||
// const u = 'http://' + this.getIp() + ':' + server.address().port + '/';
|
||||
// const DefaultMediaReceiver : any = require('castv2-client').DefaultMediaReceiver;
|
||||
client.launch(CiderReceiver, (err: any, player: any) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
let media = {
|
||||
// Here you can plug an URL to any mp4, webm, mp3 or jpg file with the proper contentType.
|
||||
contentId: "http://" + this.getIp() + ":" + this.ciderPort + "/audio.wav",
|
||||
contentType: "audio/wav",
|
||||
streamType: "LIVE", // or LIVE
|
||||
|
||||
// Title and cover displayed while buffering
|
||||
metadata: {
|
||||
type: 0,
|
||||
metadataType: 3,
|
||||
title: song ?? "",
|
||||
albumName: album ?? "",
|
||||
artist: artist ?? "",
|
||||
images: [{ url: albumart ?? "" }],
|
||||
},
|
||||
};
|
||||
|
||||
player.on("status", (status: any) => {
|
||||
console.log("status broadcast playerState=%s", status);
|
||||
});
|
||||
|
||||
console.log('app "%s" launched, loading media %s ...', player, media);
|
||||
|
||||
player.load(
|
||||
media,
|
||||
{
|
||||
autoplay: true,
|
||||
},
|
||||
(err: any, status: any) => {
|
||||
console.log("media loaded playerState=%s", status);
|
||||
}
|
||||
);
|
||||
|
||||
client.getStatus((x: any, status: any) => {
|
||||
if (status && status.volume) {
|
||||
client.volume = status.volume.level;
|
||||
client.muted = status.volume.muted;
|
||||
client.stepInterval = status.volume.stepInterval;
|
||||
}
|
||||
});
|
||||
|
||||
// send websocket ip
|
||||
|
||||
player.sendIp("ws://" + this.getIp() + ":26369");
|
||||
electron.ipcMain.on("stopGCast", (_event) => {
|
||||
player.kill();
|
||||
});
|
||||
electron.app.on("before-quit", (_event) => {
|
||||
player.kill();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getIp() {
|
||||
let ip: string = "";
|
||||
let ip2: any = [];
|
||||
let alias = 0;
|
||||
const ifaces: any = os.networkInterfaces();
|
||||
for (let dev in ifaces) {
|
||||
ifaces[dev].forEach((details: any) => {
|
||||
if (details.family === "IPv4" && !details.internal) {
|
||||
if (!/(loopback|vmware|internal|hamachi|vboxnet|virtualbox)/gi.test(dev + (alias ? ":" + alias : ""))) {
|
||||
if (details.address.substring(0, 8) === "192.168." || details.address.substring(0, 7) === "172.16." || details.address.substring(0, 3) === "10.") {
|
||||
if (
|
||||
!ip.startsWith("192.168.") ||
|
||||
(ip2.startsWith("192.168.") && !ip.startsWith("192.168.") && ip2.startsWith("172.16.") && !ip.startsWith("192.168.") && !ip.startsWith("172.16.")) ||
|
||||
(ip2.startsWith("10.") && !ip.startsWith("192.168.") && !ip.startsWith("172.16.") && !ip.startsWith("10."))
|
||||
) {
|
||||
ip = details.address;
|
||||
}
|
||||
++alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
private stream(device: any, song: any, artist: any, album: any, albumart: any) {
|
||||
let castMode = "googlecast";
|
||||
let UPNPDesc = "";
|
||||
castMode = device.type;
|
||||
UPNPDesc = device.location;
|
||||
|
||||
let client;
|
||||
if (castMode === "googlecast") {
|
||||
let client = new this.audioClient();
|
||||
client.volume = 100;
|
||||
client.stepInterval = 0.5;
|
||||
client.muted = false;
|
||||
|
||||
client.connect(device.host, () => {
|
||||
// console.log('connected, launching app ...', 'http://' + this.getIp() + ':' + server.address().port + '/');
|
||||
if (!this.connectedHosts[device.host]) {
|
||||
this.connectedHosts[device.host] = client;
|
||||
this.activeConnections.push(client);
|
||||
}
|
||||
this.loadMedia(client, song, artist, album, albumart);
|
||||
});
|
||||
|
||||
client.on("close", () => {
|
||||
console.info("Client Closed");
|
||||
for (let i = this.activeConnections.length - 1; i >= 0; i--) {
|
||||
if (this.activeConnections[i] === client) {
|
||||
this.activeConnections.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client.on("error", (err: any) => {
|
||||
console.log("Error: %s", err.message);
|
||||
client.close();
|
||||
delete this.connectedHosts[device.host];
|
||||
});
|
||||
} else {
|
||||
// upnp devices
|
||||
//try {
|
||||
// client = new MediaRendererClient(UPNPDesc);
|
||||
// const options = {
|
||||
// autoplay: true,
|
||||
// contentType: 'audio/x-wav',
|
||||
// dlnaFeatures: 'DLNA.ORG_PN=-;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000',
|
||||
// metadata: {
|
||||
// title: 'Apple Music Electron',
|
||||
// creator: 'Streaming ...',
|
||||
// type: 'audio', // can be 'video', 'audio' or 'image'
|
||||
// // url: 'http://' + getIp() + ':' + server.address().port + '/',
|
||||
// // protocolInfo: 'DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000;
|
||||
// }
|
||||
// };
|
||||
// client.load('http://' + getIp() + ':' + server.address().port + '/a.wav', options, function (err, _result) {
|
||||
// if (err) throw err;
|
||||
// console.log('playing ...');
|
||||
// });
|
||||
// } catch (e) {
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private async setupGCServer() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = "Chromecast";
|
||||
public description: string = "LastFM plugin for Cider";
|
||||
public version: string = "0.0.1";
|
||||
public author: string = "vapormusic / Cider Collective";
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: { getApp: () => any; getStore: () => any }) {
|
||||
this._app = utils.getApp();
|
||||
this._store = utils.getStore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(win: any): void {
|
||||
this._win = win;
|
||||
electron.ipcMain.on("getKnownCastDevices", (event) => {
|
||||
event.returnValue = this.castDevices;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("performGCCast", (event, device, song, artist, album, albumart) => {
|
||||
// this.setupGCServer().then( () => {
|
||||
this._win.webContents.setAudioMuted(true);
|
||||
console.log(device);
|
||||
this.stream(device, song, artist, album, albumart);
|
||||
// })
|
||||
});
|
||||
|
||||
electron.ipcMain.on("getChromeCastDevices", (_event, _data) => {
|
||||
this.searchForGCDevices();
|
||||
});
|
||||
|
||||
electron.ipcMain.on("stopGCast", (_event) => {
|
||||
this._win.webContents.setAudioMuted(false);
|
||||
this.activeConnections.forEach((client: any) => {
|
||||
try {
|
||||
client.stop();
|
||||
} catch (e) {}
|
||||
});
|
||||
this.activeConnections = [];
|
||||
this.connectedHosts = {};
|
||||
});
|
||||
}
|
||||
|
||||
let browser = this.mdns.createBrowser(this.mdns.tcp('googlecast'));
|
||||
browser.on('ready', browser.discover);
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {}
|
||||
|
||||
browser.on('update', (service: any) => {
|
||||
if (service.addresses && service.fullname && service.fullname.includes('_googlecast._tcp')) {
|
||||
let a = service.txt.filter((u: any) => String(u).startsWith('fn='))
|
||||
let name = (((a[0] ?? "").substring(3)) != "") ? ((a[0] ?? "").substring(3)) : (service.fullname.substring(0, service.fullname.indexOf("._googlecast")) )
|
||||
this.ondeviceup(service.addresses[0], name+ " (" + (service.type[0]?.description ?? "") + ")" , '', 'googlecast');
|
||||
}
|
||||
});
|
||||
const Client = require('node-ssdp').Client;
|
||||
// also do a SSDP/UPnP search
|
||||
let ssdpBrowser = new Client();
|
||||
ssdpBrowser.on('response', (headers: any, statusCode: any, rinfo: any) => {
|
||||
var location = getLocation(headers);
|
||||
if (location != null) {
|
||||
this.getServiceDescription(location, rinfo.address);
|
||||
}
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: any): void {}
|
||||
|
||||
});
|
||||
|
||||
function getLocation(headers: any) {
|
||||
let location = null;
|
||||
if (headers["LOCATION"] != null) {
|
||||
location = headers["LOCATION"]
|
||||
} else if (headers["Location"] != null) {
|
||||
location = headers["Location"]
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
ssdpBrowser.search('urn:dial-multiscreen-org:device:dial:1');
|
||||
|
||||
// // actual upnp devices
|
||||
// if (app.cfg.get("audio.enableDLNA")) {
|
||||
// let ssdpBrowser2 = new Client();
|
||||
// ssdpBrowser2.on('response', (headers, statusCode, rinfo) => {
|
||||
// var location = getLocation(headers);
|
||||
// if (location != null) {
|
||||
// this.getServiceDescription(location, rinfo.address);
|
||||
// }
|
||||
|
||||
// });
|
||||
// ssdpBrowser2.search('urn:schemas-upnp-org:device:MediaRenderer:1');
|
||||
|
||||
// }
|
||||
|
||||
|
||||
} catch (e) {
|
||||
console.log('Search GC err', e);
|
||||
}
|
||||
}
|
||||
|
||||
private getServiceDescription(url: any, address: any) {
|
||||
const request = require('request');
|
||||
request.get(url, (error: any, response: any, body: any) => {
|
||||
if (!error && response.statusCode === 200) {
|
||||
this.parseServiceDescription(body, address, url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ondeviceup(host: any, name: any, location: any, type: any) {
|
||||
if (this.castDevices.findIndex((item: any) => item.host === host && item.name === name && item.location === location && item.type === type) === -1) {
|
||||
this.castDevices.push({
|
||||
name: name,
|
||||
host: host,
|
||||
location: location,
|
||||
type: type
|
||||
});
|
||||
if (this.devices.indexOf(host) === -1) {
|
||||
this.devices.push(host);
|
||||
}
|
||||
if (name) {
|
||||
this._win.webContents.executeJavaScript(`console.log('deviceFound','ip: ${host} name:${name}')`).catch((err: any) => console.error(err));
|
||||
console.log("deviceFound", host, name);
|
||||
}
|
||||
} else {
|
||||
this._win.webContents.executeJavaScript(`console.log('deviceFound (added)','ip: ${host} name:${name}')`).catch((err: any) => console.error(err));
|
||||
console.log("deviceFound (added)", host, name);
|
||||
}
|
||||
}
|
||||
|
||||
private parseServiceDescription(body: any, address: any, url: any) {
|
||||
const parseString = require('xml2js').parseString;
|
||||
parseString(body, (err: any, result: any) => {
|
||||
if (!err && result && result.root && result.root.device) {
|
||||
const device = result.root.device[0];
|
||||
console.log('device', device);
|
||||
let devicetype = 'googlecast';
|
||||
console.log()
|
||||
if (device.deviceType && device.deviceType.toString() === 'urn:schemas-upnp-org:device:MediaRenderer:1') {
|
||||
devicetype = 'upnp';
|
||||
}
|
||||
this.ondeviceup(address, device.friendlyName.toString(), url, devicetype);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private loadMedia(client: any, song: any, artist: any, album: any, albumart: any, cb?: any) {
|
||||
// const u = 'http://' + this.getIp() + ':' + server.address().port + '/';
|
||||
// const DefaultMediaReceiver : any = require('castv2-client').DefaultMediaReceiver;
|
||||
client.launch(CiderReceiver, (err: any, player: any) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
let media = {
|
||||
// Here you can plug an URL to any mp4, webm, mp3 or jpg file with the proper contentType.
|
||||
contentId: 'http://' + this.getIp() + ':'+ this.ciderPort +'/audio.wav',
|
||||
contentType: 'audio/wav',
|
||||
streamType: 'LIVE', // or LIVE
|
||||
|
||||
// Title and cover displayed while buffering
|
||||
metadata: {
|
||||
type: 0,
|
||||
metadataType: 3,
|
||||
title: song ?? "",
|
||||
albumName: album ?? "",
|
||||
artist: artist ?? "",
|
||||
images: [
|
||||
{url: albumart ?? ""}]
|
||||
}
|
||||
};
|
||||
|
||||
player.on('status', (status: any) => {
|
||||
console.log('status broadcast playerState=%s', status);
|
||||
});
|
||||
|
||||
console.log('app "%s" launched, loading media %s ...', player, media);
|
||||
|
||||
player.load(media, {
|
||||
autoplay: true
|
||||
}, (err: any, status: any) => {
|
||||
console.log('media loaded playerState=%s', status);
|
||||
});
|
||||
|
||||
|
||||
client.getStatus((x: any, status: any) => {
|
||||
if (status && status.volume) {
|
||||
client.volume = status.volume.level;
|
||||
client.muted = status.volume.muted;
|
||||
client.stepInterval = status.volume.stepInterval;
|
||||
}
|
||||
})
|
||||
|
||||
// send websocket ip
|
||||
|
||||
player.sendIp("ws://" + this.getIp() + ":26369");
|
||||
electron.ipcMain.on('stopGCast', (_event) => {
|
||||
player.kill();
|
||||
})
|
||||
electron.app.on('before-quit', (_event) => {
|
||||
player.kill();
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private getIp(){
|
||||
let ip: string = '';
|
||||
let ip2: any = [];
|
||||
let alias = 0;
|
||||
const ifaces: any = os.networkInterfaces();
|
||||
for (let dev in ifaces) {
|
||||
ifaces[dev].forEach((details: any) => {
|
||||
if (details.family === 'IPv4' && !details.internal) {
|
||||
if (!/(loopback|vmware|internal|hamachi|vboxnet|virtualbox)/gi.test(dev + (alias ? ':' + alias : ''))) {
|
||||
if (details.address.substring(0, 8) === '192.168.' ||
|
||||
details.address.substring(0, 7) === '172.16.' ||
|
||||
details.address.substring(0, 3) === '10.'
|
||||
) {
|
||||
if (!ip.startsWith('192.168.') ||
|
||||
(ip2.startsWith('192.168.') && !ip.startsWith('192.168.')) &&
|
||||
(ip2.startsWith('172.16.') && !ip.startsWith('192.168.') && !ip.startsWith('172.16.')) ||
|
||||
(ip2.startsWith('10.') && !ip.startsWith('192.168.') && !ip.startsWith('172.16.') && !ip.startsWith('10.'))
|
||||
){ip = details.address;}
|
||||
++alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
private stream(device: any, song: any, artist: any, album: any, albumart: any) {
|
||||
let castMode = 'googlecast';
|
||||
let UPNPDesc = '';
|
||||
castMode = device.type;
|
||||
UPNPDesc = device.location;
|
||||
|
||||
let client;
|
||||
if (castMode === 'googlecast') {
|
||||
let client = new this.audioClient();
|
||||
client.volume = 100;
|
||||
client.stepInterval = 0.5;
|
||||
client.muted = false;
|
||||
|
||||
client.connect(device.host, () => {
|
||||
// console.log('connected, launching app ...', 'http://' + this.getIp() + ':' + server.address().port + '/');
|
||||
if (!this.connectedHosts[device.host]) {
|
||||
this.connectedHosts[device.host] = client;
|
||||
this.activeConnections.push(client);
|
||||
}
|
||||
this.loadMedia(client, song, artist, album, albumart);
|
||||
});
|
||||
|
||||
client.on('close', () => {
|
||||
console.info("Client Closed");
|
||||
for (let i = this.activeConnections.length - 1; i >= 0; i--) {
|
||||
if (this.activeConnections[i] === client) {
|
||||
this.activeConnections.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client.on('error', (err: any) => {
|
||||
console.log('Error: %s', err.message);
|
||||
client.close();
|
||||
delete this.connectedHosts[device.host];
|
||||
});
|
||||
|
||||
} else {
|
||||
// upnp devices
|
||||
//try {
|
||||
// client = new MediaRendererClient(UPNPDesc);
|
||||
// const options = {
|
||||
// autoplay: true,
|
||||
// contentType: 'audio/x-wav',
|
||||
// dlnaFeatures: 'DLNA.ORG_PN=-;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000',
|
||||
// metadata: {
|
||||
// title: 'Apple Music Electron',
|
||||
// creator: 'Streaming ...',
|
||||
// type: 'audio', // can be 'video', 'audio' or 'image'
|
||||
// // url: 'http://' + getIp() + ':' + server.address().port + '/',
|
||||
// // protocolInfo: 'DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000;
|
||||
// }
|
||||
// };
|
||||
|
||||
// client.load('http://' + getIp() + ':' + server.address().port + '/a.wav', options, function (err, _result) {
|
||||
// if (err) throw err;
|
||||
// console.log('playing ...');
|
||||
// });
|
||||
|
||||
// } catch (e) {
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private async setupGCServer() {
|
||||
return ''
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = 'Chromecast';
|
||||
public description: string = 'LastFM plugin for Cider';
|
||||
public version: string = '0.0.1';
|
||||
public author: string = 'vapormusic / Cider Collective';
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: { getApp: () => any; getStore: () => any; }) {
|
||||
this._app = utils.getApp();
|
||||
this._store = utils.getStore()
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(win: any): void {
|
||||
this._win = win;
|
||||
electron.ipcMain.on('getKnownCastDevices', (event) => {
|
||||
event.returnValue = this.castDevices
|
||||
});
|
||||
|
||||
electron.ipcMain.on('performGCCast', (event, device, song, artist, album, albumart) => {
|
||||
// this.setupGCServer().then( () => {
|
||||
this._win.webContents.setAudioMuted(true);
|
||||
console.log(device);
|
||||
this.stream(device, song, artist, album, albumart);
|
||||
// })
|
||||
});
|
||||
|
||||
electron.ipcMain.on('getChromeCastDevices', (_event, _data) => {
|
||||
this.searchForGCDevices();
|
||||
});
|
||||
|
||||
electron.ipcMain.on('stopGCast', (_event) => {
|
||||
this._win.webContents.setAudioMuted(false);
|
||||
this.activeConnections.forEach((client: any) => {
|
||||
try{
|
||||
client.stop();
|
||||
} catch(e){}
|
||||
})
|
||||
this.activeConnections = [];
|
||||
this.connectedHosts = {};
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: any): void {
|
||||
|
||||
}
|
||||
|
||||
onRendererReady(): void {
|
||||
this._win.webContents.executeJavaScript(
|
||||
`ipcRenderer.sendSync('get-port')`
|
||||
).then((result: any) => {
|
||||
this.ciderPort = result;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
onRendererReady(): void {
|
||||
this._win.webContents.executeJavaScript(`ipcRenderer.sendSync('get-port')`).then((result: any) => {
|
||||
this.ciderPort = result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,304 +1,299 @@
|
|||
import {AutoClient} from 'discord-auto-rpc'
|
||||
import {ipcMain} from "electron";
|
||||
import fetch from 'electron-fetch'
|
||||
import { AutoClient } from "discord-auto-rpc";
|
||||
import { ipcMain } from "electron";
|
||||
import fetch from "electron-fetch";
|
||||
|
||||
export default class DiscordRPC {
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = "Discord Rich Presence";
|
||||
public description: string = "Discord RPC plugin for Cider";
|
||||
public version: string = "1.1.0";
|
||||
public author: string = "vapormusic/Core (Cider Collective)";
|
||||
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = 'Discord Rich Presence';
|
||||
public description: string = 'Discord RPC plugin for Cider';
|
||||
public version: string = '1.1.0';
|
||||
public author: string = 'vapormusic/Core (Cider Collective)';
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private _utils: any;
|
||||
private _attributes: any;
|
||||
private ready: boolean = false;
|
||||
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private _utils: any;
|
||||
private _attributes: any;
|
||||
private ready: boolean = false;
|
||||
/**
|
||||
* Plugin Initialization
|
||||
*/
|
||||
private _client: any = null;
|
||||
private _activityCache: any = {
|
||||
details: "",
|
||||
state: "",
|
||||
largeImageKey: "",
|
||||
largeImageText: "",
|
||||
smallImageKey: "",
|
||||
smallImageText: "",
|
||||
instance: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Plugin Initialization
|
||||
*/
|
||||
private _client: any = null;
|
||||
private _activityCache: any = {
|
||||
details: '',
|
||||
state: '',
|
||||
largeImageKey: '',
|
||||
largeImageText: '',
|
||||
smallImageKey: '',
|
||||
smallImageText: '',
|
||||
instance: false
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: any) {
|
||||
this._utils = utils;
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(_win: any): void {
|
||||
const self = this;
|
||||
this.connect();
|
||||
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||
ipcMain.on("updateRPCImage", async (_event, imageurl) => {
|
||||
if (!this._utils.getStoreValue("general.privateEnabled")) {
|
||||
let b64data = "";
|
||||
let postbody = "";
|
||||
if (imageurl.startsWith("/ciderlocalart")) {
|
||||
let port = await _win.webContents.executeJavaScript(`app.clientPort`);
|
||||
console.log("http://localhost:" + port + imageurl);
|
||||
const response = await fetch("http://localhost:" + port + imageurl);
|
||||
b64data = (await response.buffer()).toString("base64");
|
||||
postbody = JSON.stringify({ data: b64data });
|
||||
fetch("https://api.cider.sh/v1/images", {
|
||||
method: "POST",
|
||||
body: postbody,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": _win.webContents.getUserAgent(),
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(function (json) {
|
||||
self._attributes["artwork"]["url"] = json.url;
|
||||
self.setActivity(self._attributes);
|
||||
});
|
||||
} else {
|
||||
postbody = JSON.stringify({ url: imageurl });
|
||||
fetch("https://api.cider.sh/v1/images", {
|
||||
method: "POST",
|
||||
body: postbody,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": _win.webContents.getUserAgent(),
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(function (json) {
|
||||
self._attributes["artwork"]["url"] = json.url;
|
||||
self.setActivity(self._attributes);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
ipcMain.on("reloadRPC", () => {
|
||||
console.log(`[DiscordRPC][reload] Reloading DiscordRPC.`);
|
||||
this._client.destroy();
|
||||
|
||||
this._client
|
||||
.endlessLogin({
|
||||
clientId: this._utils.getStoreValue("connectivity.discord_rpc.client") === "Cider" ? "911790844204437504" : "886578863147192350",
|
||||
})
|
||||
.then(() => {
|
||||
this.ready = true;
|
||||
this._utils.getWindow().webContents.send("rpcReloaded", this._client.user);
|
||||
if (this._activityCache && this._activityCache.details && this._activityCache.state) {
|
||||
console.info(`[DiscordRPC][reload] Restoring activity cache.`);
|
||||
this._client.setActivity(this._activityCache);
|
||||
}
|
||||
})
|
||||
.catch((e: any) => console.error(`[DiscordRPC][reload] ${e}`));
|
||||
// this.connect(true)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {
|
||||
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: object): void {
|
||||
this._attributes = attributes;
|
||||
this.setActivity(attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
this._attributes = attributes;
|
||||
this.setActivity(attributes);
|
||||
}
|
||||
|
||||
/*******************************************************************************************
|
||||
* Private Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Connect to Discord RPC
|
||||
* @private
|
||||
*/
|
||||
private connect() {
|
||||
if (!this._utils.getStoreValue("connectivity.discord_rpc.enabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the client
|
||||
this._client = new AutoClient({ transport: "ipc" });
|
||||
|
||||
// Runs on Ready
|
||||
this._client.once("ready", () => {
|
||||
console.info(`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${this._client.user.id}.`);
|
||||
|
||||
if (this._activityCache && this._activityCache.details && this._activityCache.state) {
|
||||
console.info(`[DiscordRPC][connect] Restoring activity cache.`);
|
||||
this._client.setActivity(this._activityCache);
|
||||
}
|
||||
});
|
||||
|
||||
// Login to Discord
|
||||
this._client
|
||||
.endlessLogin({
|
||||
clientId: this._utils.getStoreValue("connectivity.discord_rpc.client") === "Cider" ? "911790844204437504" : "886578863147192350",
|
||||
})
|
||||
.then(() => {
|
||||
this.ready = true;
|
||||
})
|
||||
.catch((e: any) => console.error(`[DiscordRPC][connect] ${e}`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the activity
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
private setActivity(attributes: any) {
|
||||
if (!this._client) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if show buttons is (true) or (false)
|
||||
let activity: Object = {
|
||||
details: this._utils.getStoreValue("connectivity.discord_rpc.details_format"),
|
||||
state: this._utils.getStoreValue("connectivity.discord_rpc.state_format"),
|
||||
largeImageKey: attributes?.artwork?.url?.replace("{w}", "1024").replace("{h}", "1024"),
|
||||
largeImageText: attributes.albumName,
|
||||
instance: false, // Whether the activity is in a game session
|
||||
};
|
||||
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
// Filter the activity
|
||||
activity = this.filterActivity(activity, attributes);
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: any) {
|
||||
this._utils = utils;
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
if (!this.ready) {
|
||||
this._activityCache = activity;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the activity
|
||||
if (!attributes.status && this._utils.getStoreValue("connectivity.discord_rpc.clear_on_pause")) {
|
||||
this._client.clearActivity();
|
||||
} else if (activity && this._activityCache !== activity) {
|
||||
this._client.setActivity(activity);
|
||||
}
|
||||
this._activityCache = activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the Discord activity object
|
||||
*/
|
||||
private filterActivity(activity: any, attributes: any): Object {
|
||||
// Add the buttons if people want them
|
||||
if (!this._utils.getStoreValue("connectivity.discord_rpc.hide_buttons")) {
|
||||
activity.buttons = [
|
||||
{ label: "Listen on Cider", url: attributes.url.cider },
|
||||
{ label: "View on Apple Music", url: attributes.url.appleMusic },
|
||||
]; //To change attributes.url => preload/cider-preload.js
|
||||
}
|
||||
|
||||
// Add the timestamp if its playing and people want them
|
||||
if (!this._utils.getStoreValue("connectivity.discord_rpc.hide_timestamp") && attributes.status) {
|
||||
activity.startTimestamp = Date.now() - (attributes?.durationInMillis - attributes?.remainingTime);
|
||||
activity.endTimestamp = attributes.endTime;
|
||||
}
|
||||
|
||||
// If the user wants to keep the activity when paused
|
||||
if (!this._utils.getStoreValue("connectivity.discord_rpc.clear_on_pause")) {
|
||||
activity.smallImageKey = attributes.status ? "play" : "pause";
|
||||
activity.smallImageText = attributes.status ? "Playing" : "Paused";
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
* Works with:
|
||||
* {artist}
|
||||
* {composer}
|
||||
* {title}
|
||||
* {album}
|
||||
* {trackNumber}
|
||||
*/
|
||||
onReady(_win: any): void {
|
||||
const self = this
|
||||
this.connect();
|
||||
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||
ipcMain.on('updateRPCImage', async (_event, imageurl) => {
|
||||
if (!this._utils.getStoreValue("general.privateEnabled")) {
|
||||
let b64data = ""
|
||||
let postbody = ""
|
||||
if (imageurl.startsWith("/ciderlocalart")){
|
||||
let port = await _win.webContents.executeJavaScript(
|
||||
`app.clientPort`
|
||||
);
|
||||
console.log("http://localhost:"+port+imageurl)
|
||||
const response = await fetch("http://localhost:"+port+imageurl)
|
||||
b64data = (await response.buffer()).toString('base64');
|
||||
postbody = JSON.stringify({data: b64data})
|
||||
fetch('https://api.cider.sh/v1/images', {
|
||||
const rpcVars: any = {
|
||||
artist: attributes.artistName,
|
||||
composer: attributes.composerName,
|
||||
title: attributes.name,
|
||||
album: attributes.albumName,
|
||||
trackNumber: attributes.trackNumber,
|
||||
};
|
||||
|
||||
method: 'POST',
|
||||
body: postbody,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': _win.webContents.getUserAgent()
|
||||
},
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(function (json) {
|
||||
self._attributes["artwork"]["url"] = json.url
|
||||
self.setActivity(self._attributes)
|
||||
})
|
||||
} else {
|
||||
postbody = JSON.stringify({url: imageurl})
|
||||
fetch('https://api.cider.sh/v1/images', {
|
||||
// Replace the variables
|
||||
Object.keys(rpcVars).forEach((key) => {
|
||||
if (activity.details.includes(`{${key}}`)) {
|
||||
activity.details = activity.details.replace(`{${key}}`, rpcVars[key]);
|
||||
}
|
||||
if (activity.state.includes(`{${key}}`)) {
|
||||
activity.state = activity.state.replace(`{${key}}`, rpcVars[key]);
|
||||
}
|
||||
});
|
||||
|
||||
method: 'POST',
|
||||
body: postbody,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': _win.webContents.getUserAgent()
|
||||
},
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(function (json) {
|
||||
self._attributes["artwork"]["url"] = json.url
|
||||
self.setActivity(self._attributes)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
ipcMain.on("reloadRPC", () => {
|
||||
console.log(`[DiscordRPC][reload] Reloading DiscordRPC.`);
|
||||
this._client.destroy()
|
||||
|
||||
this._client.endlessLogin({clientId: this._utils.getStoreValue("connectivity.discord_rpc.client") === "Cider" ? '911790844204437504' : '886578863147192350'})
|
||||
.then(() => {
|
||||
this.ready = true
|
||||
this._utils.getWindow().webContents.send("rpcReloaded", this._client.user)
|
||||
if (this._activityCache && this._activityCache.details && this._activityCache.state) {
|
||||
console.info(`[DiscordRPC][reload] Restoring activity cache.`);
|
||||
this._client.setActivity(this._activityCache)
|
||||
}
|
||||
})
|
||||
.catch((e: any) => console.error(`[DiscordRPC][reload] ${e}`));
|
||||
// this.connect(true)
|
||||
})
|
||||
// Checks if the details is greater than 128 because some songs can be that long
|
||||
if (activity.details && activity.details.length >= 128) {
|
||||
activity.details = activity.details.substring(0, 125) + "...";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {
|
||||
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||
// Checks if the state is greater than 128 because some songs can be that long
|
||||
if (activity.state && activity.state.length >= 128) {
|
||||
activity.state = activity.state.substring(0, 125) + "...";
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: object): void {
|
||||
this._attributes = attributes
|
||||
this.setActivity(attributes)
|
||||
|
||||
// Checks if the state is greater than 128 because some songs can be that long
|
||||
if (activity.largeImageText && activity.largeImageText.length >= 128) {
|
||||
activity.largeImageText = activity.largeImageText.substring(0, 125) + "...";
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
this._attributes = attributes
|
||||
this.setActivity(attributes)
|
||||
|
||||
// Check large image
|
||||
if (activity.largeImageKey == null || activity.largeImageKey === "" || activity.largeImageKey.length > 256) {
|
||||
activity.largeImageKey = "cider";
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************************
|
||||
* Private Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Connect to Discord RPC
|
||||
* @private
|
||||
*/
|
||||
private connect() {
|
||||
if (!this._utils.getStoreValue("connectivity.discord_rpc.enabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the client
|
||||
this._client = new AutoClient({transport: "ipc"});
|
||||
|
||||
// Runs on Ready
|
||||
this._client.once('ready', () => {
|
||||
console.info(`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${this._client.user.id}.`);
|
||||
|
||||
if (this._activityCache && this._activityCache.details && this._activityCache.state) {
|
||||
console.info(`[DiscordRPC][connect] Restoring activity cache.`);
|
||||
this._client.setActivity(this._activityCache)
|
||||
}
|
||||
})
|
||||
|
||||
// Login to Discord
|
||||
this._client.endlessLogin({clientId: this._utils.getStoreValue("connectivity.discord_rpc.client") === "Cider" ? '911790844204437504' : '886578863147192350'})
|
||||
.then(() => {
|
||||
this.ready = true
|
||||
})
|
||||
.catch((e: any) => console.error(`[DiscordRPC][connect] ${e}`));
|
||||
// Timestamp
|
||||
if (new Date(attributes.endTime).getTime() < 0) {
|
||||
delete activity.startTime;
|
||||
delete activity.endTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the activity
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
private setActivity(attributes: any) {
|
||||
if (!this._client) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if show buttons is (true) or (false)
|
||||
let activity: Object = {
|
||||
details: this._utils.getStoreValue("connectivity.discord_rpc.details_format"),
|
||||
state: this._utils.getStoreValue("connectivity.discord_rpc.state_format"),
|
||||
largeImageKey: attributes?.artwork?.url?.replace('{w}', '1024').replace('{h}', '1024'),
|
||||
largeImageText: attributes.albumName,
|
||||
instance: false // Whether the activity is in a game session
|
||||
}
|
||||
|
||||
// Filter the activity
|
||||
activity = this.filterActivity(activity, attributes)
|
||||
|
||||
if (!this.ready) {
|
||||
this._activityCache = activity
|
||||
return
|
||||
}
|
||||
|
||||
// Set the activity
|
||||
if (!attributes.status && this._utils.getStoreValue("connectivity.discord_rpc.clear_on_pause")) {
|
||||
this._client.clearActivity()
|
||||
} else if (activity && this._activityCache !== activity) {
|
||||
this._client.setActivity(activity)
|
||||
}
|
||||
this._activityCache = activity;
|
||||
// not sure
|
||||
if (!attributes.artistName) {
|
||||
delete activity.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the Discord activity object
|
||||
*/
|
||||
private filterActivity(activity: any, attributes: any): Object {
|
||||
|
||||
// Add the buttons if people want them
|
||||
if (!this._utils.getStoreValue("connectivity.discord_rpc.hide_buttons")) {
|
||||
activity.buttons = [
|
||||
{label: 'Listen on Cider', url: attributes.url.cider},
|
||||
{label: 'View on Apple Music', url: attributes.url.appleMusic}
|
||||
] //To change attributes.url => preload/cider-preload.js
|
||||
}
|
||||
|
||||
// Add the timestamp if its playing and people want them
|
||||
if (!this._utils.getStoreValue("connectivity.discord_rpc.hide_timestamp") && attributes.status) {
|
||||
activity.startTimestamp = Date.now() - (attributes?.durationInMillis - attributes?.remainingTime)
|
||||
activity.endTimestamp = attributes.endTime
|
||||
}
|
||||
|
||||
// If the user wants to keep the activity when paused
|
||||
if (!this._utils.getStoreValue("connectivity.discord_rpc.clear_on_pause")) {
|
||||
activity.smallImageKey = attributes.status ? 'play' : 'pause';
|
||||
activity.smallImageText = attributes.status ? 'Playing' : 'Paused';
|
||||
}
|
||||
|
||||
/**
|
||||
* Works with:
|
||||
* {artist}
|
||||
* {composer}
|
||||
* {title}
|
||||
* {album}
|
||||
* {trackNumber}
|
||||
*/
|
||||
const rpcVars: any = {
|
||||
"artist": attributes.artistName,
|
||||
"composer": attributes.composerName,
|
||||
"title": attributes.name,
|
||||
"album": attributes.albumName,
|
||||
"trackNumber": attributes.trackNumber
|
||||
}
|
||||
|
||||
// Replace the variables
|
||||
Object.keys(rpcVars).forEach((key) => {
|
||||
if (activity.details.includes(`{${key}}`)) {
|
||||
activity.details = activity.details.replace(`{${key}}`, rpcVars[key])
|
||||
}
|
||||
if (activity.state.includes(`{${key}}`)) {
|
||||
activity.state = activity.state.replace(`{${key}}`, rpcVars[key])
|
||||
}
|
||||
})
|
||||
|
||||
// Checks if the details is greater than 128 because some songs can be that long
|
||||
if (activity.details && activity.details.length >= 128) {
|
||||
activity.details = activity.details.substring(0, 125) + '...'
|
||||
}
|
||||
|
||||
// Checks if the state is greater than 128 because some songs can be that long
|
||||
if (activity.state && activity.state.length >= 128) {
|
||||
activity.state = activity.state.substring(0, 125) + '...'
|
||||
}
|
||||
|
||||
// Checks if the state is greater than 128 because some songs can be that long
|
||||
if (activity.largeImageText && activity.largeImageText.length >= 128) {
|
||||
activity.largeImageText = activity.largeImageText.substring(0, 125) + '...'
|
||||
}
|
||||
|
||||
// Check large image
|
||||
if (activity.largeImageKey == null || activity.largeImageKey === "" || activity.largeImageKey.length > 256) {
|
||||
activity.largeImageKey = "cider";
|
||||
}
|
||||
|
||||
// Timestamp
|
||||
if (new Date(attributes.endTime).getTime() < 0) {
|
||||
delete activity.startTime
|
||||
delete activity.endTime
|
||||
}
|
||||
|
||||
// not sure
|
||||
if (!attributes.artistName) {
|
||||
delete activity.state;
|
||||
}
|
||||
|
||||
if (!activity.largeImageText || activity.largeImageText.length < 2) {
|
||||
delete activity.largeImageText
|
||||
}
|
||||
return activity
|
||||
if (!activity.largeImageText || activity.largeImageText.length < 2) {
|
||||
delete activity.largeImageText;
|
||||
}
|
||||
return activity;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,236 +1,245 @@
|
|||
export default class lastfm {
|
||||
/**
|
||||
* Base Plugin Information
|
||||
*/
|
||||
public name: string = "LastFM Plugin";
|
||||
public version: string = "2.0.0";
|
||||
public author: string = "Core (Cider Collective)";
|
||||
|
||||
/**
|
||||
* Base Plugin Information
|
||||
*/
|
||||
public name: string = 'LastFM Plugin';
|
||||
public version: string = '2.0.0';
|
||||
public author: string = 'Core (Cider Collective)';
|
||||
private _apiCredentials = {
|
||||
key: "f9986d12aab5a0fe66193c559435ede3",
|
||||
secret: "acba3c29bd5973efa38cc2f0b63cc625",
|
||||
};
|
||||
/**
|
||||
* Plugin Initialization
|
||||
*/
|
||||
private _lfm: any = null;
|
||||
private _authenticated: boolean = false;
|
||||
private _scrobbleDelay: any = null;
|
||||
private _utils: any = null;
|
||||
private _scrobbleCache: any = {};
|
||||
private _nowPlayingCache: any = {};
|
||||
|
||||
/**
|
||||
* Public Methods
|
||||
*/
|
||||
|
||||
private _apiCredentials = {
|
||||
key: "f9986d12aab5a0fe66193c559435ede3",
|
||||
secret: "acba3c29bd5973efa38cc2f0b63cc625"
|
||||
constructor(utils: any) {
|
||||
this._utils = utils;
|
||||
}
|
||||
|
||||
onReady(_win: Electron.BrowserWindow): void {
|
||||
this.initializeLastFM("", this._apiCredentials);
|
||||
|
||||
// Register the ipcMain handlers
|
||||
this._utils.getIPCMain().handle("lastfm:url", (event: any) => {
|
||||
console.debug(`[${lastfm.name}:url] Called.`);
|
||||
return this._lfm.getAuthenticationUrl({ cb: "cider://auth/lastfm" });
|
||||
});
|
||||
|
||||
this._utils.getIPCMain().on("lastfm:auth", (event: any, token: string) => {
|
||||
console.debug(`[${lastfm.name}:auth] Token: `, token);
|
||||
this.authenticateLastFM(token);
|
||||
});
|
||||
|
||||
this._utils.getIPCMain().on("lastfm:disconnect", (_event: any) => {
|
||||
this._lfm.setSessionCredentials(null, null);
|
||||
this._authenticated = false;
|
||||
console.debug(`[${lastfm.name}:disconnect] Disconnected`);
|
||||
});
|
||||
|
||||
this._utils.getIPCMain().on("lastfm:nowPlayingChange", (event: any, attributes: any) => {
|
||||
if (this._utils.getStoreValue("connectivity.lastfm.filter_loop") || this._utils.getStoreValue("general.privateEnabled")) return;
|
||||
this.updateNowPlayingTrack(attributes);
|
||||
});
|
||||
|
||||
this._utils.getIPCMain().on("lastfm:scrobbleTrack", (event: any, attributes: any) => {
|
||||
if (this._utils.getStoreValue("general.privateEnabled")) return;
|
||||
this.scrobbleTrack(attributes);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: object): void {}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
* @param scrobble
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: any, scrobble = false): void {
|
||||
if (this._utils.getStoreValue("general.privateEnabled")) return;
|
||||
this.updateNowPlayingTrack(attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize LastFM
|
||||
* @param token
|
||||
* @param api
|
||||
* @private
|
||||
*/
|
||||
private initializeLastFM(token: string, api: { key: string; secret: string }): void {
|
||||
console.debug(`[${lastfm.name}:initialize] Initializing LastFM`);
|
||||
const LastfmAPI = require("lastfmapi");
|
||||
this._lfm = new LastfmAPI({
|
||||
api_key: api.key,
|
||||
secret: api.secret,
|
||||
});
|
||||
|
||||
if (this._utils.getStoreValue("connectivity.lastfm.secrets.username") && this._utils.getStoreValue("connectivity.lastfm.secrets.key")) {
|
||||
this._lfm.setSessionCredentials(this._utils.getStoreValue("connectivity.lastfm.secrets.username"), this._utils.getStoreValue("connectivity.lastfm.secrets.key"));
|
||||
this._authenticated = true;
|
||||
} else {
|
||||
this.authenticateLastFM(token);
|
||||
}
|
||||
/**
|
||||
* Plugin Initialization
|
||||
*/
|
||||
private _lfm: any = null;
|
||||
private _authenticated: boolean = false;
|
||||
private _scrobbleDelay: any = null;
|
||||
private _utils: any = null;
|
||||
private _scrobbleCache: any = {};
|
||||
private _nowPlayingCache: any = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Public Methods
|
||||
*/
|
||||
/**
|
||||
* Authenticate the user with the given token
|
||||
* @param token
|
||||
* @private
|
||||
*/
|
||||
private authenticateLastFM(token: string): void {
|
||||
if (!token) return;
|
||||
this._lfm.authenticate(token, (err: any, session: any) => {
|
||||
if (err) {
|
||||
console.error(`[${lastfm.name}:authenticate] Error: ${typeof err === "string" ? err : err.message}`);
|
||||
|
||||
constructor(utils: any) {
|
||||
this._utils = utils;
|
||||
}
|
||||
this._utils.getWindow().webContents.executeJavaScript(`app.notyf.error("${err.message}");`);
|
||||
return;
|
||||
}
|
||||
this._utils.getWindow().webContents.send("lastfm:authenticated", session);
|
||||
this._authenticated = true;
|
||||
console.debug(`[${lastfm.name}:authenticate] Authenticated as ${session.username}`);
|
||||
});
|
||||
}
|
||||
|
||||
onReady(_win: Electron.BrowserWindow): void {
|
||||
this.initializeLastFM("", this._apiCredentials)
|
||||
/**
|
||||
* Verifies the track information with lastfm
|
||||
* @param attributes
|
||||
* @param callback
|
||||
* @private
|
||||
*/
|
||||
private verifyTrack(attributes: any, callback: Function): void {
|
||||
if (!attributes) return attributes;
|
||||
|
||||
// Register the ipcMain handlers
|
||||
this._utils.getIPCMain().handle('lastfm:url', (event: any) => {
|
||||
console.debug(`[${lastfm.name}:url] Called.`)
|
||||
return this._lfm.getAuthenticationUrl({"cb": "cider://auth/lastfm"})
|
||||
})
|
||||
|
||||
this._utils.getIPCMain().on('lastfm:auth', (event: any, token: string) => {
|
||||
console.debug(`[${lastfm.name}:auth] Token: `, token)
|
||||
this.authenticateLastFM(token)
|
||||
})
|
||||
|
||||
this._utils.getIPCMain().on('lastfm:disconnect', (_event: any) => {
|
||||
this._lfm.setSessionCredentials(null, null);
|
||||
this._authenticated = false;
|
||||
console.debug(`[${lastfm.name}:disconnect] Disconnected`)
|
||||
})
|
||||
|
||||
this._utils.getIPCMain().on('lastfm:nowPlayingChange', (event: any, attributes: any) => {
|
||||
if (this._utils.getStoreValue("connectivity.lastfm.filter_loop") || this._utils.getStoreValue("general.privateEnabled")) return;
|
||||
this.updateNowPlayingTrack(attributes)
|
||||
})
|
||||
|
||||
this._utils.getIPCMain().on('lastfm:scrobbleTrack', (event: any, attributes: any) => {
|
||||
if (this._utils.getStoreValue("general.privateEnabled")) return;
|
||||
this.scrobbleTrack(attributes)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: object): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
* @param scrobble
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: any, scrobble = false): void {
|
||||
if (this._utils.getStoreValue("general.privateEnabled")) return;
|
||||
this.updateNowPlayingTrack(attributes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize LastFM
|
||||
* @param token
|
||||
* @param api
|
||||
* @private
|
||||
*/
|
||||
private initializeLastFM(token: string, api: { key: string, secret: string }): void {
|
||||
console.debug(`[${lastfm.name}:initialize] Initializing LastFM`)
|
||||
const LastfmAPI = require("lastfmapi")
|
||||
this._lfm = new LastfmAPI({
|
||||
'api_key': api.key,
|
||||
'secret': api.secret,
|
||||
});
|
||||
|
||||
if (this._utils.getStoreValue("connectivity.lastfm.secrets.username") && this._utils.getStoreValue("connectivity.lastfm.secrets.key")) {
|
||||
this._lfm.setSessionCredentials(this._utils.getStoreValue("connectivity.lastfm.secrets.username"), this._utils.getStoreValue("connectivity.lastfm.secrets.key"));
|
||||
this._authenticated = true;
|
||||
} else {
|
||||
this.authenticateLastFM(token)
|
||||
if (!attributes.lfmAlbum) {
|
||||
this._lfm.album.getInfo(
|
||||
{
|
||||
artist: attributes.primaryArtist,
|
||||
album: attributes.albumName,
|
||||
},
|
||||
(err: any, data: any) => {
|
||||
if (err) {
|
||||
console.error(`[${lastfm.name}] [album.getInfo] Error: ${typeof err === "string" ? err : err.message}`);
|
||||
return {};
|
||||
}
|
||||
if (data) {
|
||||
attributes.lfmAlbum = data;
|
||||
callback(attributes);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this._lfm.track.getCorrection(attributes.primaryArtist, attributes.name, (err: any, data: any) => {
|
||||
if (err) {
|
||||
console.error(`[${lastfm.name}] [track.getCorrection] Error: ${typeof err === "string" ? err : err.message}`);
|
||||
return {};
|
||||
}
|
||||
if (data) {
|
||||
attributes.lfmTrack = data.correction.track;
|
||||
callback(attributes);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrobbles the track to lastfm
|
||||
* @param attributes
|
||||
* @private
|
||||
*/
|
||||
private scrobbleTrack(attributes: any): void {
|
||||
if (!attributes?.lfmTrack || !attributes?.lfmAlbum) {
|
||||
this.verifyTrack(attributes, (a: any) => {
|
||||
this.scrobbleTrack(a);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user with the given token
|
||||
* @param token
|
||||
* @private
|
||||
*/
|
||||
private authenticateLastFM(token: string): void {
|
||||
if (!token) return;
|
||||
this._lfm.authenticate(token, (err: any, session: any) => {
|
||||
if (err) {
|
||||
console.error(`[${lastfm.name}:authenticate] Error: ${typeof err === "string" ? err : err.message}`);
|
||||
if (
|
||||
!this._authenticated ||
|
||||
!attributes ||
|
||||
this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] ||
|
||||
(this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._scrobbleCache.track === attributes.lfmTrack.name)
|
||||
)
|
||||
return;
|
||||
|
||||
this._utils.getWindow().webContents.executeJavaScript(`app.notyf.error("${err.message}");`)
|
||||
return;
|
||||
}
|
||||
this._utils.getWindow().webContents.send('lastfm:authenticated', session)
|
||||
this._authenticated = true;
|
||||
console.debug(`[${lastfm.name}:authenticate] Authenticated as ${session.username}`)
|
||||
});
|
||||
// Scrobble
|
||||
const scrobble = {
|
||||
artist: attributes.lfmTrack.artist.name,
|
||||
track: attributes.lfmTrack.name,
|
||||
album: attributes.lfmAlbum.name,
|
||||
albumArtist: attributes.lfmAlbum.artist,
|
||||
timestamp: new Date().getTime() / 1000,
|
||||
trackNumber: attributes.trackNumber,
|
||||
duration: attributes.durationInMillis / 1000,
|
||||
};
|
||||
|
||||
// Easy Debugging
|
||||
console.debug(`[${lastfm.name}:scrobble] Scrobbling ${scrobble.artist} - ${scrobble.track}`);
|
||||
|
||||
// Scrobble the track
|
||||
this._lfm.track.scrobble(scrobble, (err: any, _res: any) => {
|
||||
if (err) {
|
||||
console.error(`[${lastfm.name}:scrobble] Scrobble failed: ${err.message}`);
|
||||
} else {
|
||||
console.debug(`[${lastfm.name}:scrobble] Track scrobbled: ${scrobble.artist} - ${scrobble.track}`);
|
||||
this._scrobbleCache = scrobble;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the now playing track
|
||||
* @param attributes
|
||||
* @private
|
||||
*/
|
||||
private updateNowPlayingTrack(attributes: any): void {
|
||||
if (!attributes?.lfmTrack || !attributes?.lfmAlbum) {
|
||||
this.verifyTrack(attributes, (a: any) => {
|
||||
this.updateNowPlayingTrack(a);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the track information with lastfm
|
||||
* @param attributes
|
||||
* @param callback
|
||||
* @private
|
||||
*/
|
||||
private verifyTrack(attributes: any, callback: Function): void {
|
||||
if (!attributes) return attributes;
|
||||
if (
|
||||
!this._authenticated ||
|
||||
!attributes ||
|
||||
this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] ||
|
||||
(this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._nowPlayingCache.track === attributes.lfmTrack.name)
|
||||
)
|
||||
return;
|
||||
|
||||
if (!attributes.lfmAlbum) {
|
||||
this._lfm.album.getInfo({
|
||||
"artist": attributes.primaryArtist,
|
||||
"album": attributes.albumName
|
||||
}, (err: any, data: any) => {
|
||||
if (err) {
|
||||
console.error(`[${lastfm.name}] [album.getInfo] Error: ${typeof err === "string" ? err : err.message}`)
|
||||
return {};
|
||||
}
|
||||
if (data) {
|
||||
attributes.lfmAlbum = data
|
||||
callback(attributes)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this._lfm.track.getCorrection(attributes.primaryArtist, attributes.name, (err: any, data: any) => {
|
||||
if (err) {
|
||||
console.error(`[${lastfm.name}] [track.getCorrection] Error: ${typeof err === "string" ? err : err.message}`)
|
||||
return {};
|
||||
}
|
||||
if (data) {
|
||||
attributes.lfmTrack = data.correction.track
|
||||
callback(attributes)
|
||||
}
|
||||
})
|
||||
}
|
||||
const nowPlaying = {
|
||||
artist: attributes.lfmTrack.artist.name,
|
||||
track: attributes.lfmTrack.name,
|
||||
album: attributes.lfmAlbum.name,
|
||||
trackNumber: attributes.trackNumber,
|
||||
duration: attributes.durationInMillis / 1000,
|
||||
albumArtist: attributes.lfmAlbum.artist,
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrobbles the track to lastfm
|
||||
* @param attributes
|
||||
* @private
|
||||
*/
|
||||
private scrobbleTrack(attributes: any): void {
|
||||
if (!attributes?.lfmTrack || !attributes?.lfmAlbum) {
|
||||
this.verifyTrack(attributes, (a: any) => {
|
||||
this.scrobbleTrack(a)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!this._authenticated || !attributes || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] || (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._scrobbleCache.track === attributes.lfmTrack.name)) return;
|
||||
|
||||
// Scrobble
|
||||
const scrobble = {
|
||||
'artist': attributes.lfmTrack.artist.name,
|
||||
'track': attributes.lfmTrack.name,
|
||||
'album': attributes.lfmAlbum.name,
|
||||
'albumArtist': attributes.lfmAlbum.artist,
|
||||
'timestamp': new Date().getTime() / 1000,
|
||||
'trackNumber': attributes.trackNumber,
|
||||
'duration': attributes.durationInMillis / 1000,
|
||||
}
|
||||
|
||||
// Easy Debugging
|
||||
console.debug(`[${lastfm.name}:scrobble] Scrobbling ${scrobble.artist} - ${scrobble.track}`)
|
||||
|
||||
// Scrobble the track
|
||||
this._lfm.track.scrobble(scrobble, (err: any, _res: any) => {
|
||||
if (err) {
|
||||
console.error(`[${lastfm.name}:scrobble] Scrobble failed: ${err.message}`);
|
||||
} else {
|
||||
console.debug(`[${lastfm.name}:scrobble] Track scrobbled: ${scrobble.artist} - ${scrobble.track}`);
|
||||
this._scrobbleCache = scrobble
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the now playing track
|
||||
* @param attributes
|
||||
* @private
|
||||
*/
|
||||
private updateNowPlayingTrack(attributes: any): void {
|
||||
if (!attributes?.lfmTrack || !attributes?.lfmAlbum) {
|
||||
this.verifyTrack(attributes, (a: any) => {
|
||||
this.updateNowPlayingTrack(a)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!this._authenticated || !attributes || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] || (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._nowPlayingCache.track === attributes.lfmTrack.name)) return;
|
||||
|
||||
const nowPlaying = {
|
||||
'artist': attributes.lfmTrack.artist.name,
|
||||
'track': attributes.lfmTrack.name,
|
||||
'album': attributes.lfmAlbum.name,
|
||||
'trackNumber': attributes.trackNumber,
|
||||
'duration': attributes.durationInMillis / 1000,
|
||||
'albumArtist': attributes.lfmAlbum.artist,
|
||||
}
|
||||
|
||||
this._lfm.track.updateNowPlaying(nowPlaying, (err: any, res: any) => {
|
||||
if (err) {
|
||||
console.error(`[${lastfm.name}:updateNowPlaying] Now Playing Update failed: ${err.message}`);
|
||||
} else {
|
||||
console.debug(`[${lastfm.name}:updateNowPlaying] Now Playing Updated: ${nowPlaying.artist} - ${nowPlaying.track}`);
|
||||
this._nowPlayingCache = nowPlaying
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
this._lfm.track.updateNowPlaying(nowPlaying, (err: any, res: any) => {
|
||||
if (err) {
|
||||
console.error(`[${lastfm.name}:updateNowPlaying] Now Playing Update failed: ${err.message}`);
|
||||
} else {
|
||||
console.debug(`[${lastfm.name}:updateNowPlaying] Now Playing Updated: ${nowPlaying.artist} - ${nowPlaying.track}`);
|
||||
this._nowPlayingCache = nowPlaying;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,355 +1,338 @@
|
|||
import {app, Menu, shell} from "electron";
|
||||
import {utils} from "../base/utils";
|
||||
import { app, Menu, shell } from "electron";
|
||||
import { utils } from "../base/utils";
|
||||
|
||||
export default class Thumbar {
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = "Menubar Plugin";
|
||||
public description: string = "Creates the menubar";
|
||||
public version: string = "1.0.0";
|
||||
public author: string = "Core";
|
||||
public contributors: string[] = ["Core", "Qwack", "Monochromish"];
|
||||
|
||||
/**
|
||||
* Menubar Assets
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = 'Menubar Plugin';
|
||||
public description: string = 'Creates the menubar';
|
||||
public version: string = '1.0.0';
|
||||
public author: string = 'Core';
|
||||
public contributors: string[] = ['Core', 'Qwack', 'Monochromish'];
|
||||
|
||||
/**
|
||||
* Menubar Assets
|
||||
* @private
|
||||
*/
|
||||
|
||||
private isNotMac: boolean = process.platform !== 'darwin';
|
||||
private isMac: boolean = process.platform === 'darwin';
|
||||
private _menuTemplate: any = [
|
||||
private isNotMac: boolean = process.platform !== "darwin";
|
||||
private isMac: boolean = process.platform === "darwin";
|
||||
private _menuTemplate: any = [
|
||||
{
|
||||
label: app.getName(),
|
||||
submenu: [
|
||||
{
|
||||
label: app.getName(),
|
||||
submenu: [
|
||||
{
|
||||
label: `${utils.getLocale(utils.getStoreValue('general.language'), 'term.about')} ${app.getName()}`,
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('about')`)
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.toggleprivate'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.togglePrivateSession").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.cfg.general.privateEnabled = !app.cfg.general.privateEnabled`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.settings'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.settings").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.openSettingsPage()`)
|
||||
},
|
||||
...(this.isMac ? [
|
||||
{type: 'separator'},
|
||||
{role: 'services'},
|
||||
{type: 'separator'},
|
||||
{role: 'hide'},
|
||||
{role: 'hideOthers'},
|
||||
{role: 'unhide'},
|
||||
{type: 'separator'},
|
||||
{role: 'quit'}
|
||||
] : []),
|
||||
...(this.isNotMac ? [
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.quit'),
|
||||
accelerator: 'Control+Q',
|
||||
click: () => app.quit()
|
||||
|
||||
}
|
||||
] : [])
|
||||
]
|
||||
label: `${utils.getLocale(utils.getStoreValue("general.language"), "term.about")} ${app.getName()}`,
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('about')`),
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.toggleprivate"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.togglePrivateSession").join("+"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.cfg.general.privateEnabled = !app.cfg.general.privateEnabled`),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.view'),
|
||||
submenu: [
|
||||
...(this.isMac ? [
|
||||
{role: 'reload'},
|
||||
{role: 'forceReload'},
|
||||
{role: 'toggleDevTools'},
|
||||
{type: 'separator'},
|
||||
{role: 'resetZoom'},
|
||||
{role: 'zoomIn'},
|
||||
{role: 'zoomOut'},
|
||||
{type: 'separator'},
|
||||
{role: 'togglefullscreen'},
|
||||
{type: 'separator'},
|
||||
] : []),
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.search'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.search").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript('app.focusSearch()')
|
||||
},
|
||||
{type:'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.listenNow'),
|
||||
accelerator: utils.getStoreValue('general.keybindings.listnow').join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('listen_now')`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.browse'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.browse").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('browse')`)
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.recentlyAdded')
|
||||
,accelerator: utils.getStoreValue("general.keybindings.recentAdd").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-recentlyadded')`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.songs'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.songs").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-songs')`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.albums'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.albums").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-albums')`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.artists'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.artists").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-artists')`)
|
||||
},
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.settings"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.settings").join("+"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.openSettingsPage()`),
|
||||
},
|
||||
...(this.isMac ? [{ type: "separator" }, { role: "services" }, { type: "separator" }, { role: "hide" }, { role: "hideOthers" }, { role: "unhide" }, { type: "separator" }, { role: "quit" }] : []),
|
||||
...(this.isNotMac
|
||||
? [
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.quit"),
|
||||
accelerator: "Control+Q",
|
||||
click: () => app.quit(),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.view"),
|
||||
submenu: [
|
||||
...(this.isMac
|
||||
? [
|
||||
{ role: "reload" },
|
||||
{ role: "forceReload" },
|
||||
{ role: "toggleDevTools" },
|
||||
{ type: "separator" },
|
||||
{ role: "resetZoom" },
|
||||
{ role: "zoomIn" },
|
||||
{ role: "zoomOut" },
|
||||
{ type: "separator" },
|
||||
{ role: "togglefullscreen" },
|
||||
{ type: "separator" },
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.search"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.search").join("+"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript("app.focusSearch()"),
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.listenNow"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.listnow").join("+"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('listen_now')`),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.window'),
|
||||
submenu: [
|
||||
{role: 'minimize', label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.minimize')},
|
||||
{type: 'separator'},
|
||||
...(this.isMac ? [
|
||||
{
|
||||
label: 'Show',
|
||||
click: () => utils.getWindow().show()
|
||||
},
|
||||
{role: 'zoom'},
|
||||
{type: 'separator'},
|
||||
{role: 'front'},
|
||||
{role: 'close'},
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{role: 'undo'},
|
||||
{role: 'redo'},
|
||||
{type: 'separator'},
|
||||
{role: 'cut'},
|
||||
{role: 'copy'},
|
||||
{role: 'paste'},
|
||||
]
|
||||
},
|
||||
{type: 'separator'},
|
||||
] : [ ]),
|
||||
...(this.isNotMac ? [
|
||||
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.zoom'),
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.zoomin'),
|
||||
role: 'zoomIn',
|
||||
accelerator: utils.getStoreValue("general.keybindings.zoomn").join('+')
|
||||
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.zoomout'),
|
||||
role: 'zoomOut',
|
||||
accelerator: utils.getStoreValue("general.keybindings.zoomt").join('+')
|
||||
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.zoomreset'),
|
||||
role: 'resetZoom',
|
||||
accelerator: utils.getStoreValue("general.keybindings.zoomrst").join('+')
|
||||
}
|
||||
]
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.fullscreen'),
|
||||
accelerator: 'Control+Enter',
|
||||
role: 'togglefullscreen'
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'action.close'),
|
||||
accelerator: 'Control+W',
|
||||
role: 'close'
|
||||
},
|
||||
{type:'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.reload'),
|
||||
accelerator: 'Control+R',
|
||||
role: 'reload'
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.forcereload'),
|
||||
accelerator: 'Control+Shift+R',
|
||||
role: 'forceReload'
|
||||
},
|
||||
] : []),
|
||||
],
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.browse"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.browse").join("+"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('browse')`),
|
||||
},
|
||||
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.controls'),
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.playpause'),
|
||||
accelerator: 'Space',
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.SpacePause()`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.next'),
|
||||
accelerator: 'CommandOrControl+Right',
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.next()`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.previous'),
|
||||
accelerator: 'CommandOrControl+Left',
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.previous()`)
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.volumeup'),
|
||||
accelerator: 'CommandOrControl+Up',
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.volumeUp()`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.volumedown'),
|
||||
accelerator: 'CommandOrControl+Down',
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.volumeDown()`)
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.cast2'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.castToDevices").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.modals.castMenu = true`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.webremote'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.webRemote").join('+'),
|
||||
sublabel: 'Opens in external window',
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('remote-pair')`)
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.audioSettings'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.audioSettings").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.modals.audioSettings = true`)
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.plugins'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.pluginMenu").join('+'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.modals.pluginMenu = true`)
|
||||
}
|
||||
|
||||
]
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.recentlyAdded"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.recentAdd").join("+"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-recentlyadded')`),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.account'),
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.accountSettings'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('apple-account-settings')`)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.signout'),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.unauthorize()`)
|
||||
}
|
||||
]
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.songs"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.songs").join("+"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-songs')`),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.support'),
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.discord'),
|
||||
click: () => shell.openExternal("https://discord.gg/AppleMusic").catch(console.error)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.github'),
|
||||
click: () => shell.openExternal("https://github.com/ciderapp/Cider/wiki/Troubleshooting").catch(console.error)
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.report'),
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.bug'),
|
||||
click: () => shell.openExternal("https://github.com/ciderapp/Cider/issues/new?assignees=&labels=bug%2Ctriage&template=bug_report.yaml&title=%5BBug%5D%3A+").catch(console.error)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.feature'),
|
||||
click: () => shell.openExternal("https://github.com/ciderapp/Cider/discussions/new?category=feature-request").catch(console.error)
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.trans'),
|
||||
click: () => shell.openExternal("https://github.com/ciderapp/Cider/issues/new?assignees=&labels=%F0%9F%8C%90+Translations&template=translation.yaml&title=%5BTranslation%5D%3A+").catch(console.error)
|
||||
},
|
||||
]
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.license'),
|
||||
click: () => shell.openExternal("https://github.com/ciderapp/Cider/blob/main/LICENSE").catch(console.error)
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.toggledevtools'),
|
||||
accelerator: utils.getStoreValue("general.keybindings.openDeveloperTools").join('+'),
|
||||
click: () => utils.getWindow().webContents.openDevTools()
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.conf'),
|
||||
click: () => utils.getStoreInstance().openInEditor()
|
||||
}
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.albums"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.albums").join("+"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-albums')`),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.artists"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.artists").join("+"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-artists')`),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.window"),
|
||||
submenu: [
|
||||
{
|
||||
role: "minimize",
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.minimize"),
|
||||
},
|
||||
{ type: "separator" },
|
||||
...(this.isMac
|
||||
? [
|
||||
{
|
||||
label: "Show",
|
||||
click: () => utils.getWindow().show(),
|
||||
},
|
||||
{ role: "zoom" },
|
||||
{ type: "separator" },
|
||||
{ role: "front" },
|
||||
{ role: "close" },
|
||||
{
|
||||
label: "Edit",
|
||||
submenu: [{ role: "undo" }, { role: "redo" }, { type: "separator" }, { role: "cut" }, { role: "copy" }, { role: "paste" }],
|
||||
},
|
||||
{ type: "separator" },
|
||||
]
|
||||
}
|
||||
];
|
||||
: []),
|
||||
...(this.isNotMac
|
||||
? [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.zoom"),
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.zoomin"),
|
||||
role: "zoomIn",
|
||||
accelerator: utils.getStoreValue("general.keybindings.zoomn").join("+"),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.zoomout"),
|
||||
role: "zoomOut",
|
||||
accelerator: utils.getStoreValue("general.keybindings.zoomt").join("+"),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.zoomreset"),
|
||||
role: "resetZoom",
|
||||
accelerator: utils.getStoreValue("general.keybindings.zoomrst").join("+"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.fullscreen"),
|
||||
accelerator: "Control+Enter",
|
||||
role: "togglefullscreen",
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "action.close"),
|
||||
accelerator: "Control+W",
|
||||
role: "close",
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.reload"),
|
||||
accelerator: "Control+R",
|
||||
role: "reload",
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.forcereload"),
|
||||
accelerator: "Control+Shift+R",
|
||||
role: "forceReload",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.controls"),
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.playpause"),
|
||||
accelerator: "Space",
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.SpacePause()`),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.next"),
|
||||
accelerator: "CommandOrControl+Right",
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.next()`),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.previous"),
|
||||
accelerator: "CommandOrControl+Left",
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.previous()`),
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.volumeup"),
|
||||
accelerator: "CommandOrControl+Up",
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.volumeUp()`),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.volumedown"),
|
||||
accelerator: "CommandOrControl+Down",
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.volumeDown()`),
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.cast2"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.castToDevices").join("+"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.modals.castMenu = true`),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.webremote"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.webRemote").join("+"),
|
||||
sublabel: "Opens in external window",
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('remote-pair')`),
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.audioSettings"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.audioSettings").join("+"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.modals.audioSettings = true`),
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.plugins"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.pluginMenu").join("+"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.modals.pluginMenu = true`),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.account"),
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.accountSettings"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('apple-account-settings')`),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.signout"),
|
||||
click: () => utils.getWindow().webContents.executeJavaScript(`app.unauthorize()`),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.support"),
|
||||
role: "help",
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.discord"),
|
||||
click: () => shell.openExternal("https://discord.gg/AppleMusic").catch(console.error),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "term.github"),
|
||||
click: () => shell.openExternal("https://github.com/ciderapp/Cider/wiki/Troubleshooting").catch(console.error),
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.report"),
|
||||
submenu: [
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.bug"),
|
||||
click: () => shell.openExternal("https://github.com/ciderapp/Cider/issues/new?assignees=&labels=bug%2Ctriage&template=bug_report.yaml&title=%5BBug%5D%3A+").catch(console.error),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.feature"),
|
||||
click: () => shell.openExternal("https://github.com/ciderapp/Cider/discussions/new?category=feature-request").catch(console.error),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.trans"),
|
||||
click: () => shell.openExternal("https://github.com/ciderapp/Cider/issues/new?assignees=&labels=%F0%9F%8C%90+Translations&template=translation.yaml&title=%5BTranslation%5D%3A+").catch(console.error),
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.license"),
|
||||
click: () => shell.openExternal("https://github.com/ciderapp/Cider/blob/main/LICENSE").catch(console.error),
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.toggledevtools"),
|
||||
accelerator: utils.getStoreValue("general.keybindings.openDeveloperTools").join("+"),
|
||||
click: () => utils.getWindow().webContents.openDevTools(),
|
||||
},
|
||||
{
|
||||
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.conf"),
|
||||
click: () => utils.getStoreInstance().openInEditor(),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(_utils: utils) {
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
}
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(_win: Electron.BrowserWindow): void {
|
||||
const menu = Menu.buildFromTemplate(this._menuTemplate);
|
||||
Menu.setApplicationMenu(menu)
|
||||
}
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(_utils: utils) {
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {
|
||||
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||
}
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(_win: Electron.BrowserWindow): void {
|
||||
const menu = Menu.buildFromTemplate(this._menuTemplate);
|
||||
Menu.setApplicationMenu(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: object): void {
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {
|
||||
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
|
||||
}
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: object): void {}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: object): void {}
|
||||
}
|
||||
|
|
|
@ -1,177 +1,176 @@
|
|||
// @ts-ignore
|
||||
import * as Player from 'mpris-service';
|
||||
import * as Player from "mpris-service";
|
||||
|
||||
export default class mpris {
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private static utils: any;
|
||||
/**
|
||||
* MPRIS Service
|
||||
*/
|
||||
private static player: Player.Player;
|
||||
/**
|
||||
* 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';
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private static utils: any;
|
||||
/**
|
||||
* MPRIS Service
|
||||
*/
|
||||
private static player: Player.Player;
|
||||
/**
|
||||
* 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";
|
||||
|
||||
/*******************************************************************************************
|
||||
* Private Methods
|
||||
* ****************************************************************************************/
|
||||
/*******************************************************************************************
|
||||
* Private Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: any) {
|
||||
mpris.utils = utils
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: any) {
|
||||
mpris.utils = utils;
|
||||
|
||||
console.debug(`[Plugin][${mpris.name}] Loading Complete.`);
|
||||
console.debug(`[Plugin][${mpris.name}] Loading Complete.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks non-linux systems from running this plugin
|
||||
* @private
|
||||
* @decorator
|
||||
*/
|
||||
private static linuxOnly(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
if (process.platform !== "linux") {
|
||||
descriptor.value = function () {
|
||||
return;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks non-linux systems from running this plugin
|
||||
* @private
|
||||
* @decorator
|
||||
*/
|
||||
private static linuxOnly(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
if (process.platform !== 'linux') {
|
||||
descriptor.value = function () {
|
||||
return
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Connects to MPRIS Service
|
||||
*/
|
||||
private static connect() {
|
||||
const player = Player({
|
||||
name: "cider",
|
||||
identity: "Cider",
|
||||
supportedInterfaces: ["player"],
|
||||
});
|
||||
|
||||
console.debug(`[${mpris.name}:connect] Successfully connected.`);
|
||||
|
||||
const renderer = mpris.utils.getWindow().webContents;
|
||||
const loopType: { [key: string]: number } = {
|
||||
none: 0,
|
||||
track: 1,
|
||||
playlist: 2,
|
||||
};
|
||||
|
||||
player.on("next", () => mpris.utils.playback.next());
|
||||
player.on("previous", () => mpris.utils.playback.previous());
|
||||
player.on("playpause", () => mpris.utils.playback.playPause());
|
||||
player.on("play", () => mpris.utils.playback.play());
|
||||
player.on("pause", () => mpris.utils.playback.pause());
|
||||
player.on("quit", () => mpris.utils.getApp().exit());
|
||||
player.on("position", (args: { position: any }) => mpris.utils.playback.seek(args.position / 1000 / 1000));
|
||||
player.on("loopStatus", (status: string) => renderer.executeJavaScript(`app.mk.repeatMode = ${loopType[status.toLowerCase()]}`));
|
||||
player.on("shuffle", () => renderer.executeJavaScript("app.mk.shuffleMode = (app.mk.shuffleMode === 0) ? 1 : 0"));
|
||||
|
||||
mpris.utils.getIPCMain().on("mpris:playbackTimeDidChange", (event: any, time: number) => {
|
||||
player.getPosition = () => time;
|
||||
});
|
||||
|
||||
mpris.utils.getIPCMain().on("repeatModeDidChange", (_e: any, mode: number) => {
|
||||
switch (mode) {
|
||||
case 0:
|
||||
player.loopStatus = Player.LOOP_STATUS_NONE;
|
||||
break;
|
||||
case 1:
|
||||
player.loopStatus = Player.LOOP_STATUS_TRACK;
|
||||
break;
|
||||
case 2:
|
||||
player.loopStatus = Player.LOOP_STATUS_PLAYLIST;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
mpris.utils.getIPCMain().on("shuffleModeDidChange", (_e: any, mode: number) => {
|
||||
player.shuffle = mode === 1;
|
||||
});
|
||||
|
||||
mpris.player = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update M.P.R.I.S Player Attributes
|
||||
*/
|
||||
private static updateMetaData(attributes: any) {
|
||||
mpris.player.metadata = {
|
||||
"mpris:trackid": mpris.player.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,
|
||||
};
|
||||
}
|
||||
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Clear state
|
||||
* @private
|
||||
*/
|
||||
private static clearState() {
|
||||
if (!mpris.player) {
|
||||
return;
|
||||
}
|
||||
mpris.player.metadata = {
|
||||
"mpris:trackid": "/org/mpris/MediaPlayer2/TrackList/NoTrack",
|
||||
};
|
||||
mpris.player.playbackStatus = Player.PLAYBACK_STATUS_STOPPED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to MPRIS Service
|
||||
*/
|
||||
private static connect() {
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
@mpris.linuxOnly
|
||||
onReady(_: any): void {
|
||||
console.debug(`[${mpris.name}:onReady] Ready.`);
|
||||
}
|
||||
|
||||
const player = Player({
|
||||
name: 'cider',
|
||||
identity: 'Cider',
|
||||
supportedInterfaces: ['player']
|
||||
});
|
||||
/**
|
||||
* Renderer ready
|
||||
*/
|
||||
@mpris.linuxOnly
|
||||
onRendererReady(): void {
|
||||
mpris.connect();
|
||||
}
|
||||
|
||||
console.debug(`[${mpris.name}:connect] Successfully connected.`);
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
@mpris.linuxOnly
|
||||
onBeforeQuit(): void {
|
||||
console.debug(`[Plugin][${mpris.name}] Stopped.`);
|
||||
mpris.clearState();
|
||||
}
|
||||
|
||||
const renderer = mpris.utils.getWindow().webContents
|
||||
const loopType: { [key: string]: number; } = {
|
||||
'none': 0,
|
||||
'track': 1,
|
||||
'playlist': 2,
|
||||
}
|
||||
|
||||
player.on('next', () => mpris.utils.playback.next())
|
||||
player.on('previous', () => mpris.utils.playback.previous())
|
||||
player.on('playpause', () => mpris.utils.playback.playPause())
|
||||
player.on('play', () => mpris.utils.playback.play())
|
||||
player.on('pause', () => mpris.utils.playback.pause())
|
||||
player.on('quit', () => mpris.utils.getApp().exit())
|
||||
player.on('position', (args: { position: any; }) => mpris.utils.playback.seek(args.position / 1000 / 1000))
|
||||
player.on('loopStatus', (status: string) => renderer.executeJavaScript(`app.mk.repeatMode = ${loopType[status.toLowerCase()]}`))
|
||||
player.on('shuffle', () => renderer.executeJavaScript('app.mk.shuffleMode = (app.mk.shuffleMode === 0) ? 1 : 0'))
|
||||
|
||||
mpris.utils.getIPCMain().on('mpris:playbackTimeDidChange', (event: any, time: number) => {
|
||||
player.getPosition = () => time;
|
||||
})
|
||||
|
||||
mpris.utils.getIPCMain().on('repeatModeDidChange', (_e: any, mode: number) => {
|
||||
switch (mode) {
|
||||
case 0:
|
||||
player.loopStatus = Player.LOOP_STATUS_NONE;
|
||||
break;
|
||||
case 1:
|
||||
player.loopStatus = Player.LOOP_STATUS_TRACK;
|
||||
break;
|
||||
case 2:
|
||||
player.loopStatus = Player.LOOP_STATUS_PLAYLIST;
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
mpris.utils.getIPCMain().on('shuffleModeDidChange', (_e: any, mode: number) => {
|
||||
player.shuffle = mode === 1
|
||||
})
|
||||
|
||||
mpris.player = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update M.P.R.I.S Player Attributes
|
||||
*/
|
||||
private static updateMetaData(attributes: any) {
|
||||
|
||||
mpris.player.metadata = {
|
||||
'mpris:trackid': mpris.player.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
|
||||
};
|
||||
}
|
||||
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Clear state
|
||||
* @private
|
||||
*/
|
||||
private static clearState() {
|
||||
if (!mpris.player) {
|
||||
return
|
||||
}
|
||||
mpris.player.metadata = {'mpris:trackid': '/org/mpris/MediaPlayer2/TrackList/NoTrack'}
|
||||
mpris.player.playbackStatus = Player.PLAYBACK_STATUS_STOPPED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
@mpris.linuxOnly
|
||||
onReady(_: any): void {
|
||||
console.debug(`[${mpris.name}:onReady] Ready.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderer ready
|
||||
*/
|
||||
@mpris.linuxOnly
|
||||
onRendererReady(): void {
|
||||
mpris.connect()
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
@mpris.linuxOnly
|
||||
onBeforeQuit(): void {
|
||||
console.debug(`[Plugin][${mpris.name}] Stopped.`);
|
||||
mpris.clearState()
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
@mpris.linuxOnly
|
||||
onPlaybackStateDidChange(attributes: any): void {
|
||||
mpris.player.playbackStatus = attributes?.status ? Player.PLAYBACK_STATUS_PLAYING : Player.PLAYBACK_STATUS_PAUSED
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
@mpris.linuxOnly
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
mpris.updateMetaData(attributes);
|
||||
}
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
@mpris.linuxOnly
|
||||
onPlaybackStateDidChange(attributes: any): void {
|
||||
mpris.player.playbackStatus = attributes?.status ? Player.PLAYBACK_STATUS_PLAYING : Player.PLAYBACK_STATUS_PAUSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
@mpris.linuxOnly
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
mpris.updateMetaData(attributes);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,124 +1,118 @@
|
|||
import fetch from "electron-fetch";
|
||||
import {app, nativeImage, Notification} from "electron";
|
||||
import { app, nativeImage, Notification } from "electron";
|
||||
import NativeImage = Electron.NativeImage;
|
||||
import {createWriteStream} from "fs";
|
||||
import {join} from "path";
|
||||
import { createWriteStream } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
export default class playbackNotifications {
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = "Playback Notifications";
|
||||
public description: string = "Creates notifications on playback.";
|
||||
public version: string = "1.0.0";
|
||||
public author: string = "Core";
|
||||
public contributors: string[] = ["Core", "Monochromish"];
|
||||
|
||||
private _utils: any;
|
||||
private _notification: Notification | undefined;
|
||||
private _artworkImage: { [key: string]: NativeImage } = {};
|
||||
private _artworkNums: Array<string> = [];
|
||||
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = 'Playback Notifications';
|
||||
public description: string = 'Creates notifications on playback.';
|
||||
public version: string = '1.0.0';
|
||||
public author: string = 'Core';
|
||||
public contributors: string[] = ['Core', 'Monochromish'];
|
||||
/**
|
||||
* Creates playback notification
|
||||
* @param a: Music Attributes
|
||||
*/
|
||||
createNotification(a: any): void {
|
||||
if (this._notification) {
|
||||
this._notification.close();
|
||||
}
|
||||
|
||||
private _utils: any;
|
||||
private _notification: Notification | undefined;
|
||||
private _artworkImage: { [key: string]: NativeImage } = {};
|
||||
private _artworkNums: Array<string> = [];
|
||||
|
||||
/**
|
||||
* Creates playback notification
|
||||
* @param a: Music Attributes
|
||||
*/
|
||||
createNotification(a: any): void {
|
||||
if (this._notification) {
|
||||
this._notification.close();
|
||||
}
|
||||
|
||||
this._notification = new Notification({
|
||||
title: a.name,
|
||||
body: `${a.artistName} — ${a.albumName}`,
|
||||
silent: true,
|
||||
icon: this._artworkImage[a.artwork.url],
|
||||
urgency: 'low',
|
||||
actions: [
|
||||
{
|
||||
'type': 'button',
|
||||
'text': `${this._utils.getLocale(this._utils.getStoreValue('general.language'), 'term.skip')}`
|
||||
}
|
||||
],
|
||||
toastXml: `
|
||||
this._notification = new Notification({
|
||||
title: a.name,
|
||||
body: `${a.artistName} — ${a.albumName}`,
|
||||
silent: true,
|
||||
icon: this._artworkImage[a.artwork.url],
|
||||
urgency: "low",
|
||||
actions: [
|
||||
{
|
||||
type: "button",
|
||||
text: `${this._utils.getLocale(this._utils.getStoreValue("general.language"), "term.skip")}`,
|
||||
},
|
||||
],
|
||||
toastXml: `
|
||||
<toast>
|
||||
<audio silent="true" />
|
||||
<visual>
|
||||
<binding template="ToastImageAndText02">
|
||||
<image id="1" src="${join(app.getPath("temp"), `${a.songId}-${a.artwork.url.split('/').pop()}`)}" name="Image" />
|
||||
<text id="1">${a?.name.replace(/&/g, '&')}</text>
|
||||
<text id="2">${a?.artistName.replace(/&/g, '&')} — ${a?.albumName.replace(/&/g, '&')}</text>
|
||||
<image id="1" src="${join(app.getPath("temp"), `${a.songId}-${a.artwork.url.split("/").pop()}`)}" name="Image" />
|
||||
<text id="1">${a?.name.replace(/&/g, "&")}</text>
|
||||
<text id="2">${a?.artistName.replace(/&/g, "&")} — ${a?.albumName.replace(/&/g, "&")}</text>
|
||||
</binding>
|
||||
</visual>
|
||||
<actions>
|
||||
<action content="${this._utils.getLocale(this._utils.getStoreValue('general.language'), 'term.playpause')}" activationType="protocol" arguments="cider://playpause/"/>
|
||||
<action content="${this._utils.getLocale(this._utils.getStoreValue('general.language'), 'term.next')}" activationType="protocol" arguments="cider://nextitem/"/>
|
||||
<action content="${this._utils.getLocale(this._utils.getStoreValue("general.language"), "term.playpause")}" activationType="protocol" arguments="cider://playpause/"/>
|
||||
<action content="${this._utils.getLocale(this._utils.getStoreValue("general.language"), "term.next")}" activationType="protocol" arguments="cider://nextitem/"/>
|
||||
</actions>
|
||||
</toast>`
|
||||
});
|
||||
</toast>`,
|
||||
});
|
||||
|
||||
console.log(this._notification.toastXml);
|
||||
console.log(this._notification.toastXml);
|
||||
|
||||
this._notification.on('click', (_: any) => {
|
||||
this._utils.getWindow().show()
|
||||
this._utils.getWindow().focus()
|
||||
})
|
||||
this._notification.on("click", (_: any) => {
|
||||
this._utils.getWindow().show();
|
||||
this._utils.getWindow().focus();
|
||||
});
|
||||
|
||||
this._notification.on('close', (_: any) => {
|
||||
this._notification = undefined;
|
||||
})
|
||||
this._notification.on("close", (_: any) => {
|
||||
this._notification = undefined;
|
||||
});
|
||||
|
||||
this._notification.on('action', (event: any, action: any) => {
|
||||
this._utils.playback.next()
|
||||
})
|
||||
this._notification.on("action", (event: any, action: any) => {
|
||||
this._utils.playback.next();
|
||||
});
|
||||
|
||||
this._notification.show();
|
||||
this._notification.show();
|
||||
}
|
||||
|
||||
}
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: any) {
|
||||
this._utils = utils;
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
utils.getIPCMain().on("playbackNotifications:create", (event: any, a: any) => {
|
||||
a.artwork.url = a.artwork.url.replace("/{w}x{h}bb", "/512x512bb").replace("/2000x2000bb", "/35x35bb");
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: any) {
|
||||
this._utils = utils;
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
|
||||
utils.getIPCMain().on('playbackNotifications:create', (event: any, a: any) => {
|
||||
a.artwork.url = a.artwork.url.replace('/{w}x{h}bb', '/512x512bb').replace('/2000x2000bb', '/35x35bb');
|
||||
|
||||
if (this._artworkNums.length > 20) {
|
||||
delete this._artworkImage[this._artworkNums[0]];
|
||||
this._artworkNums.shift();
|
||||
}
|
||||
|
||||
if (this._artworkImage[a.artwork.url]) {
|
||||
this.createNotification(a);
|
||||
} else {
|
||||
if (process.platform === "win32") {
|
||||
fetch(a.artwork.url)
|
||||
.then(res => {
|
||||
console.log(join(app.getPath("temp"), `${a.songId}-${a.artwork.url.split('/').pop()}`));
|
||||
const dest = createWriteStream(join(app.getPath("temp"), `${a.songId}-${a.artwork.url.split('/').pop()}`));
|
||||
// @ts-ignore
|
||||
res.body.pipe(dest)
|
||||
this.createNotification(a);
|
||||
})
|
||||
} else {
|
||||
fetch(a.artwork.url).then(async blob => {
|
||||
this._artworkImage[a.artwork.url] = nativeImage.createFromBuffer(Buffer.from(await blob.arrayBuffer()));
|
||||
this._artworkNums[this._artworkNums.length] = a.artwork.url;
|
||||
this.createNotification(a);
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (this._artworkNums.length > 20) {
|
||||
delete this._artworkImage[this._artworkNums[0]];
|
||||
this._artworkNums.shift();
|
||||
}
|
||||
|
||||
if (this._artworkImage[a.artwork.url]) {
|
||||
this.createNotification(a);
|
||||
} else {
|
||||
if (process.platform === "win32") {
|
||||
fetch(a.artwork.url).then((res) => {
|
||||
console.log(join(app.getPath("temp"), `${a.songId}-${a.artwork.url.split("/").pop()}`));
|
||||
const dest = createWriteStream(join(app.getPath("temp"), `${a.songId}-${a.artwork.url.split("/").pop()}`));
|
||||
// @ts-ignore
|
||||
res.body.pipe(dest);
|
||||
this.createNotification(a);
|
||||
});
|
||||
} else {
|
||||
fetch(a.artwork.url).then(async (blob) => {
|
||||
this._artworkImage[a.artwork.url] = nativeImage.createFromBuffer(Buffer.from(await blob.arrayBuffer()));
|
||||
this._artworkNums[this._artworkNums.length] = a.artwork.url;
|
||||
this.createNotification(a);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +1,39 @@
|
|||
import * as electron from 'electron';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import { join, resolve } from 'path';
|
||||
import * as CiderReceiver from '../base/castreceiver';
|
||||
import fetch from 'electron-fetch';
|
||||
import {Stream} from "stream";
|
||||
import {spawn} from 'child_process';
|
||||
import {Worker} from 'worker_threads';
|
||||
import { Blob } from 'buffer';
|
||||
|
||||
import * as electron from "electron";
|
||||
import * as os from "os";
|
||||
import * as fs from "fs";
|
||||
import { join, resolve } from "path";
|
||||
import * as CiderReceiver from "../base/castreceiver";
|
||||
import fetch from "electron-fetch";
|
||||
import { Stream } from "stream";
|
||||
import { spawn } from "child_process";
|
||||
import { Worker } from "worker_threads";
|
||||
import { Blob } from "buffer";
|
||||
|
||||
export default class RAOP {
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private _utils: any;
|
||||
private _win: any;
|
||||
private _app: any;
|
||||
private _store: any;
|
||||
private _cacheAttr: any;
|
||||
private u: any;
|
||||
private ipairplay: any = "";
|
||||
private portairplay: any = "";
|
||||
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private _utils: any;
|
||||
private _win: any;
|
||||
private _app: any;
|
||||
private _store: any;
|
||||
private _cacheAttr: any;
|
||||
private u: any;
|
||||
private ipairplay: any = "";
|
||||
private portairplay: any = "";
|
||||
|
||||
private airtunes: any;
|
||||
private device: any;
|
||||
private mdns = require('mdns-js');
|
||||
private ok: any = 1;
|
||||
private devices: any = [];
|
||||
private castDevices: any = [];
|
||||
private i: any = false;
|
||||
private audioStream: any = new Stream.PassThrough();
|
||||
private ffmpeg: any = null;
|
||||
private worker: any = null;
|
||||
|
||||
private airtunes: any;
|
||||
private device: any;
|
||||
private mdns = require("mdns-js");
|
||||
private ok: any = 1;
|
||||
private devices: any = [];
|
||||
private castDevices: any = [];
|
||||
private i: any = false;
|
||||
private audioStream: any = new Stream.PassThrough();
|
||||
private ffmpeg: any = null;
|
||||
private worker: any = null;
|
||||
|
||||
private processNode = `
|
||||
private processNode = `
|
||||
import {parentPort, workerData} from "worker_threads";
|
||||
function getAudioConv (buffers) {
|
||||
|
||||
|
@ -88,308 +85,297 @@ export default class RAOP {
|
|||
|
||||
`;
|
||||
|
||||
private ondeviceup(name: any, host: any, port: any, addresses: any, text: any, airplay2: any = null) {
|
||||
// console.log(this.castDevices.findIndex((item: any) => {return (item.name == host.replace(".local","") && item.port == port )}))
|
||||
if (this.castDevices.findIndex((item: any) => {return (item != null && item.name == (host ?? "Unknown").replace(".local","") && item.port == port )}) == -1) {
|
||||
this.castDevices.push({
|
||||
name: (host ?? "Unknown").replace(".local",""),
|
||||
host: addresses ? addresses[0] : '',
|
||||
port: port,
|
||||
addresses: addresses,
|
||||
txt: text,
|
||||
airplay2: airplay2
|
||||
});
|
||||
if (this.devices.indexOf(host) === -1) {
|
||||
this.devices.push(host);
|
||||
}
|
||||
if (name) {
|
||||
this._win.webContents.executeJavaScript(`console.log('deviceFound','ip: ${host} name:${name}')`).catch((err: any) => console.error(err));
|
||||
console.log("deviceFound", host, name);
|
||||
}
|
||||
} else {
|
||||
this._win.webContents.executeJavaScript(`console.log('deviceFound (added)','ip: ${host} name:${name}')`).catch((err: any) => console.error(err));
|
||||
console.log("deviceFound (added)", host, name);
|
||||
private ondeviceup(name: any, host: any, port: any, addresses: any, text: any, airplay2: any = null) {
|
||||
// console.log(this.castDevices.findIndex((item: any) => {return (item.name == host.replace(".local","") && item.port == port )}))
|
||||
if (
|
||||
this.castDevices.findIndex((item: any) => {
|
||||
return item != null && item.name == (host ?? "Unknown").replace(".local", "") && item.port == port;
|
||||
}) == -1
|
||||
) {
|
||||
this.castDevices.push({
|
||||
name: (host ?? "Unknown").replace(".local", ""),
|
||||
host: addresses ? addresses[0] : "",
|
||||
port: port,
|
||||
addresses: addresses,
|
||||
txt: text,
|
||||
airplay2: airplay2,
|
||||
});
|
||||
if (this.devices.indexOf(host) === -1) {
|
||||
this.devices.push(host);
|
||||
}
|
||||
if (name) {
|
||||
this._win.webContents.executeJavaScript(`console.log('deviceFound','ip: ${host} name:${name}')`).catch((err: any) => console.error(err));
|
||||
console.log("deviceFound", host, name);
|
||||
}
|
||||
} else {
|
||||
this._win.webContents.executeJavaScript(`console.log('deviceFound (added)','ip: ${host} name:${name}')`).catch((err: any) => console.error(err));
|
||||
console.log("deviceFound (added)", host, name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = "RAOP";
|
||||
public description: string = "RAOP Plugin";
|
||||
public version: string = "0.0.1";
|
||||
public author: string = "vapormusic / Cider Collective";
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: { getStore: () => any; getApp: () => any }) {
|
||||
this._utils = utils;
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
this._app = utils.getApp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(win: any): void {
|
||||
this.u = require("airtunes2");
|
||||
this._win = win;
|
||||
|
||||
electron.ipcMain.on("getKnownAirplayDevices", (event) => {
|
||||
event.returnValue = this.castDevices;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("getAirplayDevice", (event, data) => {
|
||||
this.castDevices = [];
|
||||
console.log("scan for airplay devices");
|
||||
|
||||
const browser = this.mdns.createBrowser(this.mdns.tcp("raop"));
|
||||
browser.on("ready", browser.discover);
|
||||
|
||||
browser.on("update", (service: any) => {
|
||||
if (service.addresses && service.fullname && service.fullname.includes("_raop._tcp")) {
|
||||
// console.log(service.txt)
|
||||
this._win.webContents.executeJavaScript(`console.log(
|
||||
"${service.name} ${service.host}:${service.port} ${service.addresses}"
|
||||
)`);
|
||||
this.ondeviceup(service.name, service.host, service.port, service.addresses, service.txt);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = 'RAOP';
|
||||
public description: string = 'RAOP Plugin';
|
||||
public version: string = '0.0.1';
|
||||
public author: string = 'vapormusic / Cider Collective';
|
||||
const browser2 = this.mdns.createBrowser(this.mdns.tcp("airplay"));
|
||||
browser2.on("ready", browser2.discover);
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(utils: { getStore: () => any; getApp: () => any; }) {
|
||||
this._utils = utils;
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
this._app = utils.getApp();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(win: any): void {
|
||||
this.u = require('airtunes2');
|
||||
this._win = win;
|
||||
|
||||
electron.ipcMain.on('getKnownAirplayDevices', (event) => {
|
||||
event.returnValue = this.castDevices
|
||||
});
|
||||
|
||||
electron.ipcMain.on("getAirplayDevice", (event, data) => {
|
||||
this.castDevices = [];
|
||||
console.log("scan for airplay devices");
|
||||
|
||||
const browser = this.mdns.createBrowser(this.mdns.tcp('raop'));
|
||||
browser.on('ready', browser.discover);
|
||||
|
||||
browser.on('update', (service: any) => {
|
||||
if (service.addresses && service.fullname && (service.fullname.includes('_raop._tcp'))) {
|
||||
// console.log(service.txt)
|
||||
this._win.webContents.executeJavaScript(`console.log(
|
||||
browser2.on("update", (service: any) => {
|
||||
if (service.addresses && service.fullname && service.fullname.includes("_airplay._tcp")) {
|
||||
// console.log(service.txt)
|
||||
this._win.webContents.executeJavaScript(`console.log(
|
||||
"${service.name} ${service.host}:${service.port} ${service.addresses}"
|
||||
)`);
|
||||
this.ondeviceup(service.name, service.host, service.port, service.addresses, service.txt);
|
||||
}
|
||||
});
|
||||
this.ondeviceup(service.name, service.host, service.port, service.addresses, service.txt, true);
|
||||
}
|
||||
});
|
||||
|
||||
const browser2 = this.mdns.createBrowser(this.mdns.tcp('airplay'));
|
||||
browser2.on('ready', browser2.discover);
|
||||
// const browser2 = this.mdns.createBrowser(this.mdns.tcp('airplay'));
|
||||
// browser2.on('ready', browser2.discover);
|
||||
|
||||
browser2.on('update', (service: any) => {
|
||||
if (service.addresses && service.fullname && (service.fullname.includes('_airplay._tcp'))) {
|
||||
// console.log(service.txt)
|
||||
this._win.webContents.executeJavaScript(`console.log(
|
||||
"${service.name} ${service.host}:${service.port} ${service.addresses}"
|
||||
)`);
|
||||
this.ondeviceup(service.name, service.host, service.port, service.addresses, service.txt, true);
|
||||
}
|
||||
});
|
||||
|
||||
// const browser2 = this.mdns.createBrowser(this.mdns.tcp('airplay'));
|
||||
// browser2.on('ready', browser2.discover);
|
||||
// browser2.on('update', (service: any) => {
|
||||
// if (service.addresses && service.fullname && (service.fullname.includes('_raop._tcp') || service.fullname.includes('_airplay._tcp'))) {
|
||||
// // console.log(service.txt)
|
||||
// this._win.webContents.executeJavaScript(`console.log(
|
||||
// "${service.name} ${service.host}:${service.port} ${service.addresses}"
|
||||
// )`);
|
||||
// this.ondeviceup(service.name, service.host, service.port, service.addresses, service.txt);
|
||||
// }
|
||||
// });
|
||||
});
|
||||
|
||||
// browser2.on('update', (service: any) => {
|
||||
// if (service.addresses && service.fullname && (service.fullname.includes('_raop._tcp') || service.fullname.includes('_airplay._tcp'))) {
|
||||
// // console.log(service.txt)
|
||||
// this._win.webContents.executeJavaScript(`console.log(
|
||||
// "${service.name} ${service.host}:${service.port} ${service.addresses}"
|
||||
// )`);
|
||||
// this.ondeviceup(service.name, service.host, service.port, service.addresses, service.txt);
|
||||
// }
|
||||
// });
|
||||
|
||||
electron.ipcMain.on("performAirplayPCM", (event, ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv) => {
|
||||
if (ipv4 != this.ipairplay || ipport != this.portairplay) {
|
||||
if (this.airtunes == null) {
|
||||
this.airtunes = new this.u();
|
||||
}
|
||||
this.ipairplay = ipv4;
|
||||
this.portairplay = ipport;
|
||||
this.device = this.airtunes.add(ipv4, {
|
||||
port: ipport,
|
||||
volume: 50,
|
||||
password: sepassword,
|
||||
txt: txt,
|
||||
airplay2: airplay2dv,
|
||||
debug: true,
|
||||
});
|
||||
|
||||
|
||||
|
||||
electron.ipcMain.on("performAirplayPCM", (event, ipv4, ipport, sepassword, title, artist, album, artworkURL,txt,airplay2dv) => {
|
||||
|
||||
if (ipv4 != this.ipairplay || ipport != this.portairplay) {
|
||||
if (this.airtunes == null) { this.airtunes = new this.u()}
|
||||
this.ipairplay = ipv4;
|
||||
this.portairplay = ipport;
|
||||
this.device = this.airtunes.add(ipv4, {
|
||||
port: ipport,
|
||||
volume: 50,
|
||||
password: sepassword,
|
||||
txt: txt,
|
||||
airplay2: airplay2dv,
|
||||
debug: true
|
||||
});
|
||||
// console.log('lol',txt)
|
||||
this.device.on('status', (status: any) => {
|
||||
console.log('device status', status);
|
||||
if (status == "ready"){
|
||||
this._win.webContents.setAudioMuted(true);
|
||||
this._win.webContents.executeJavaScript(`CiderAudio.sendAudio()`).catch((err: any) => console.error(err));
|
||||
}
|
||||
if (status == "need_password"){
|
||||
this._win.webContents.executeJavaScript(`app.setAirPlayCodeUI()`)
|
||||
}
|
||||
if (status == "pair_success"){
|
||||
this._win.webContents.executeJavaScript(`app.sendAirPlaySuccess()`)
|
||||
}
|
||||
if (status == "pair_failed"){
|
||||
this._win.webContents.executeJavaScript(`app.sendAirPlayFailed()`)
|
||||
}
|
||||
if (status == 'stopped') {
|
||||
this.airtunes.stopAll(() => {
|
||||
console.log('end');
|
||||
});
|
||||
this.airtunes = null;
|
||||
this.device = null;
|
||||
this.ipairplay = '';
|
||||
this.portairplay = '';
|
||||
this.ok = 1;
|
||||
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
if (this.ok == 1) {
|
||||
console.log(this.device.key, title ?? '', artist ?? '', album ?? '');
|
||||
this.airtunes.setTrackInfo(this.device.key, title ?? '', artist?? '', album?? '');
|
||||
this.uploadImageAirplay(artworkURL);
|
||||
console.log('done');
|
||||
this.ok == 2
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
electron.ipcMain.on('setAirPlayPasscode', (event, passcode) => {
|
||||
if (this.device){
|
||||
this.device.setPasscode(passcode)
|
||||
}
|
||||
})
|
||||
|
||||
electron.ipcMain.on('writeWAV', (event, leftbuffer, rightbuffer) => {
|
||||
if (this.airtunes != null) {
|
||||
if (this.worker == null) {
|
||||
try{
|
||||
const toDataUrl = (js: any) => new URL(`data:text/javascript,${encodeURIComponent(js)}`);
|
||||
// let blob = new Blob([this.processNode], { type: 'application/javascript' });
|
||||
//Create new worker
|
||||
this.worker = new Worker(toDataUrl(this.processNode));
|
||||
|
||||
//Listen for a message from worker
|
||||
this.worker.on("message", (result: any) => {
|
||||
// fs.writeFile(join(electron.app.getPath('userData'), 'buffer.raw'), Buffer.from(Int8Array.from(result.outbuffer)),{flag: 'a+'}, function (err) {
|
||||
// if (err) throw err;
|
||||
// console.log('It\'s saved!');
|
||||
// });
|
||||
this.airtunes.circularBuffer.write(Buffer.from(Int8Array.from(result.outbuffer)));
|
||||
});
|
||||
|
||||
this.worker.on("error", (error: any) => {
|
||||
console.log("bruh",error);
|
||||
});
|
||||
this.worker.postMessage({buffer: [leftbuffer, rightbuffer]});
|
||||
} catch (e){console.log(e)}
|
||||
|
||||
|
||||
// this.ffmpeg != null ? this.ffmpeg.kill() : null;
|
||||
// this.ffmpeg = spawn(this._utils.getStoreValue("advanced.ffmpegLocation"), [
|
||||
// '-f', 's16le', // PCM 16bits, little-endian
|
||||
// '-ar', '48000',
|
||||
// '-ac', "2",
|
||||
// '-err_detect','ignore_err',
|
||||
// '-i', "http://localhost:9000/audio.wav",
|
||||
// '-acodec', 'pcm_s16le',
|
||||
// '-f', 's16le', // PCM 16bits, little-endian
|
||||
// '-ar', '44100', // Sampling rate
|
||||
// '-ac', "2", // Stereo
|
||||
// 'pipe:1' // Output on stdout
|
||||
// ]);
|
||||
|
||||
// // pipe data to AirTunes
|
||||
// this.ffmpeg.stdout.pipe(this.airtunes);
|
||||
// this.i = true;
|
||||
} else {
|
||||
this.worker.postMessage({buffer: [leftbuffer, rightbuffer]});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
electron.ipcMain.on('disconnectAirplay', (event) => {
|
||||
this._win.webContents.setAudioMuted(false);
|
||||
this.airtunes.stopAll(function () {
|
||||
console.log('end');
|
||||
// console.log('lol',txt)
|
||||
this.device.on("status", (status: any) => {
|
||||
console.log("device status", status);
|
||||
if (status == "ready") {
|
||||
this._win.webContents.setAudioMuted(true);
|
||||
this._win.webContents.executeJavaScript(`CiderAudio.sendAudio()`).catch((err: any) => console.error(err));
|
||||
}
|
||||
if (status == "need_password") {
|
||||
this._win.webContents.executeJavaScript(`app.setAirPlayCodeUI()`);
|
||||
}
|
||||
if (status == "pair_success") {
|
||||
this._win.webContents.executeJavaScript(`app.sendAirPlaySuccess()`);
|
||||
}
|
||||
if (status == "pair_failed") {
|
||||
this._win.webContents.executeJavaScript(`app.sendAirPlayFailed()`);
|
||||
}
|
||||
if (status == "stopped") {
|
||||
this.airtunes.stopAll(() => {
|
||||
console.log("end");
|
||||
});
|
||||
this.airtunes = null;
|
||||
this.device = null;
|
||||
this.ipairplay = '';
|
||||
this.portairplay = '';
|
||||
this.ipairplay = "";
|
||||
this.portairplay = "";
|
||||
this.ok = 1;
|
||||
this.i = false;
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
if (this.ok == 1) {
|
||||
console.log(this.device.key, title ?? "", artist ?? "", album ?? "");
|
||||
this.airtunes.setTrackInfo(this.device.key, title ?? "", artist ?? "", album ?? "");
|
||||
this.uploadImageAirplay(artworkURL);
|
||||
console.log("done");
|
||||
this.ok == 2;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
electron.ipcMain.on('updateAirplayInfo', (event, title, artist, album, artworkURL) => {
|
||||
if (this.airtunes && this.device) {
|
||||
console.log(this.device.key, title, artist, album);
|
||||
this.airtunes.setTrackInfo(this.device.key, title, artist, album);
|
||||
this.uploadImageAirplay(artworkURL)
|
||||
}
|
||||
});
|
||||
electron.ipcMain.on("setAirPlayPasscode", (event, passcode) => {
|
||||
if (this.device) {
|
||||
this.device.setPasscode(passcode);
|
||||
}
|
||||
});
|
||||
|
||||
electron.ipcMain.on('updateRPCImage', (_event, imageurl) => {
|
||||
this.uploadImageAirplay(imageurl)
|
||||
})
|
||||
electron.ipcMain.on("writeWAV", (event, leftbuffer, rightbuffer) => {
|
||||
if (this.airtunes != null) {
|
||||
if (this.worker == null) {
|
||||
try {
|
||||
const toDataUrl = (js: any) => new URL(`data:text/javascript,${encodeURIComponent(js)}`);
|
||||
// let blob = new Blob([this.processNode], { type: 'application/javascript' });
|
||||
//Create new worker
|
||||
this.worker = new Worker(toDataUrl(this.processNode));
|
||||
|
||||
//Listen for a message from worker
|
||||
this.worker.on("message", (result: any) => {
|
||||
// fs.writeFile(join(electron.app.getPath('userData'), 'buffer.raw'), Buffer.from(Int8Array.from(result.outbuffer)),{flag: 'a+'}, function (err) {
|
||||
// if (err) throw err;
|
||||
// console.log('It\'s saved!');
|
||||
// });
|
||||
this.airtunes.circularBuffer.write(Buffer.from(Int8Array.from(result.outbuffer)));
|
||||
});
|
||||
|
||||
this.worker.on("error", (error: any) => {
|
||||
console.log("bruh", error);
|
||||
});
|
||||
this.worker.postMessage({ buffer: [leftbuffer, rightbuffer] });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
}
|
||||
// this.ffmpeg != null ? this.ffmpeg.kill() : null;
|
||||
// this.ffmpeg = spawn(this._utils.getStoreValue("advanced.ffmpegLocation"), [
|
||||
// '-f', 's16le', // PCM 16bits, little-endian
|
||||
// '-ar', '48000',
|
||||
// '-ac', "2",
|
||||
// '-err_detect','ignore_err',
|
||||
// '-i', "http://localhost:9000/audio.wav",
|
||||
// '-acodec', 'pcm_s16le',
|
||||
// '-f', 's16le', // PCM 16bits, little-endian
|
||||
// '-ar', '44100', // Sampling rate
|
||||
// '-ac', "2", // Stereo
|
||||
// 'pipe:1' // Output on stdout
|
||||
// ]);
|
||||
|
||||
private uploadImageAirplay = (url: any) => {
|
||||
try {
|
||||
if (url != null && url != '') {
|
||||
//console.log(join(this._app.getPath('userData'), 'temp.png'), url);
|
||||
fetch(url)
|
||||
.then(res => res.buffer())
|
||||
.then((buffer) => {
|
||||
this.airtunes.setArtwork(this.device.key, buffer, "image/png");
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
});
|
||||
}
|
||||
} catch (e) { console.log(e) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {
|
||||
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Runs on song change
|
||||
// * @param attributes Music Attributes
|
||||
// */
|
||||
// onNowPlayingItemDidChange(attributes: any): void {
|
||||
// if (this.airtunes && this.device) {
|
||||
// let title = attributes.name ? attributes.name : '';
|
||||
// let artist = attributes.artistName ? attributes.artistName : '';
|
||||
// let album = attributes.albumName ? attributes.albumName : '';
|
||||
// let artworkURL = attributes?.artwork?.url?.replace('{w}', '1024').replace('{h}', '1024') ?? null;
|
||||
// console.log(this.device.key, title, artist, album);
|
||||
// this.airtunes.setTrackInfo(this.device.key, title, artist, album);
|
||||
// if (artworkURL)
|
||||
// this.uploadImageAirplay(artworkURL)
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: any): void {
|
||||
if (this.airtunes && this.device) {
|
||||
let title = attributes?.name ?? '';
|
||||
let artist = attributes?.artistName ?? '';
|
||||
let album = attributes?.albumName ?? '';
|
||||
let artworkURL = attributes?.artwork?.url ?? null;
|
||||
console.log(this.device.key, title, artist, album);
|
||||
this.airtunes.setTrackInfo(this.device.key, title, artist, album);
|
||||
if (artworkURL != null){}
|
||||
this.uploadImageAirplay(artworkURL.replace('{w}', '1024').replace('{h}', '1024'))
|
||||
// // pipe data to AirTunes
|
||||
// this.ffmpeg.stdout.pipe(this.airtunes);
|
||||
// this.i = true;
|
||||
} else {
|
||||
this.worker.postMessage({ buffer: [leftbuffer, rightbuffer] });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
electron.ipcMain.on("disconnectAirplay", (event) => {
|
||||
this._win.webContents.setAudioMuted(false);
|
||||
this.airtunes.stopAll(function () {
|
||||
console.log("end");
|
||||
});
|
||||
this.airtunes = null;
|
||||
this.device = null;
|
||||
this.ipairplay = "";
|
||||
this.portairplay = "";
|
||||
this.ok = 1;
|
||||
this.i = false;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("updateAirplayInfo", (event, title, artist, album, artworkURL) => {
|
||||
if (this.airtunes && this.device) {
|
||||
console.log(this.device.key, title, artist, album);
|
||||
this.airtunes.setTrackInfo(this.device.key, title, artist, album);
|
||||
this.uploadImageAirplay(artworkURL);
|
||||
}
|
||||
});
|
||||
|
||||
electron.ipcMain.on("updateRPCImage", (_event, imageurl) => {
|
||||
this.uploadImageAirplay(imageurl);
|
||||
});
|
||||
}
|
||||
|
||||
private uploadImageAirplay = (url: any) => {
|
||||
try {
|
||||
if (url != null && url != "") {
|
||||
//console.log(join(this._app.getPath('userData'), 'temp.png'), url);
|
||||
fetch(url)
|
||||
.then((res) => res.buffer())
|
||||
.then((buffer) => {
|
||||
this.airtunes.setArtwork(this.device.key, buffer, "image/png");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {}
|
||||
|
||||
// /**
|
||||
// * Runs on song change
|
||||
// * @param attributes Music Attributes
|
||||
// */
|
||||
// onNowPlayingItemDidChange(attributes: any): void {
|
||||
// if (this.airtunes && this.device) {
|
||||
// let title = attributes.name ? attributes.name : '';
|
||||
// let artist = attributes.artistName ? attributes.artistName : '';
|
||||
// let album = attributes.albumName ? attributes.albumName : '';
|
||||
// let artworkURL = attributes?.artwork?.url?.replace('{w}', '1024').replace('{h}', '1024') ?? null;
|
||||
// console.log(this.device.key, title, artist, album);
|
||||
// this.airtunes.setTrackInfo(this.device.key, title, artist, album);
|
||||
// if (artworkURL)
|
||||
// this.uploadImageAirplay(artworkURL)
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: any): void {
|
||||
if (this.airtunes && this.device) {
|
||||
let title = attributes?.name ?? "";
|
||||
let artist = attributes?.artistName ?? "";
|
||||
let album = attributes?.albumName ?? "";
|
||||
let artworkURL = attributes?.artwork?.url ?? null;
|
||||
console.log(this.device.key, title, artist, album);
|
||||
this.airtunes.setTrackInfo(this.device.key, title, artist, album);
|
||||
if (artworkURL != null) {
|
||||
}
|
||||
this.uploadImageAirplay(artworkURL.replace("{w}", "1024").replace("{h}", "1024"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,136 +1,134 @@
|
|||
import {nativeImage, nativeTheme} from "electron";
|
||||
import {utils} from "../base/utils";
|
||||
import {join} from "path";
|
||||
import { nativeImage, nativeTheme } from "electron";
|
||||
import { utils } from "../base/utils";
|
||||
import { join } from "path";
|
||||
|
||||
export default class Thumbar {
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private _win: any;
|
||||
private _app: any;
|
||||
/**
|
||||
* 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 = 'Thumbnail Toolbar Plugin';
|
||||
public description: string = 'Creates and managed the thumbnail toolbar buttons and their events';
|
||||
public version: string = '1.0.0';
|
||||
public author: string = 'Core';
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = "Thumbnail Toolbar Plugin";
|
||||
public description: string = "Creates and managed the thumbnail toolbar buttons and their events";
|
||||
public version: string = "1.0.0";
|
||||
public author: string = "Core";
|
||||
|
||||
/**
|
||||
* Thumbnail Toolbar Assets
|
||||
*/
|
||||
private icons: { [key: string]: Electron.NativeImage } = {
|
||||
pause: nativeImage.createFromPath(join(utils.getPath('resourcePath'), 'icons/thumbar', `${nativeTheme.shouldUseDarkColors ? 'light' : 'dark'}_pause.png`)),
|
||||
play: nativeImage.createFromPath(join(utils.getPath('resourcePath'), 'icons/thumbar', `${nativeTheme.shouldUseDarkColors ? 'light' : 'dark'}_play.png`)),
|
||||
next: nativeImage.createFromPath(join(utils.getPath('resourcePath'), 'icons/thumbar', `${nativeTheme.shouldUseDarkColors ? 'light' : 'dark'}_next.png`)),
|
||||
previous: nativeImage.createFromPath(join(utils.getPath('resourcePath'), 'icons/thumbar', `${nativeTheme.shouldUseDarkColors ? 'light' : 'dark'}_previous.png`)),
|
||||
/**
|
||||
* Thumbnail Toolbar Assets
|
||||
*/
|
||||
private icons: { [key: string]: Electron.NativeImage } = {
|
||||
pause: nativeImage.createFromPath(join(utils.getPath("resourcePath"), "icons/thumbar", `${nativeTheme.shouldUseDarkColors ? "light" : "dark"}_pause.png`)),
|
||||
play: nativeImage.createFromPath(join(utils.getPath("resourcePath"), "icons/thumbar", `${nativeTheme.shouldUseDarkColors ? "light" : "dark"}_play.png`)),
|
||||
next: nativeImage.createFromPath(join(utils.getPath("resourcePath"), "icons/thumbar", `${nativeTheme.shouldUseDarkColors ? "light" : "dark"}_next.png`)),
|
||||
previous: nativeImage.createFromPath(join(utils.getPath("resourcePath"), "icons/thumbar", `${nativeTheme.shouldUseDarkColors ? "light" : "dark"}_previous.png`)),
|
||||
};
|
||||
|
||||
/*******************************************************************************************
|
||||
* Private Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Blocks non-windows systems from running this plugin
|
||||
* @private
|
||||
* @decorator
|
||||
*/
|
||||
private static windowsOnly(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
if (process.platform !== "win32") {
|
||||
descriptor.value = function () {
|
||||
return;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the thumbnail toolbar
|
||||
*/
|
||||
private updateButtons(attributes: any) {
|
||||
console.log(attributes);
|
||||
|
||||
if (!attributes) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*******************************************************************************************
|
||||
* Private Methods
|
||||
* ****************************************************************************************/
|
||||
const buttons = [
|
||||
{
|
||||
tooltip: "Previous",
|
||||
icon: this.icons.previous,
|
||||
click() {
|
||||
utils.playback.previous();
|
||||
},
|
||||
},
|
||||
{
|
||||
tooltip: attributes.status ? "Pause" : "Play",
|
||||
icon: attributes.status ? this.icons.pause : this.icons.play,
|
||||
click() {
|
||||
utils.playback.playPause();
|
||||
},
|
||||
},
|
||||
{
|
||||
tooltip: "Next",
|
||||
icon: this.icons.next,
|
||||
click() {
|
||||
utils.playback.next();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Blocks non-windows systems from running this plugin
|
||||
* @private
|
||||
* @decorator
|
||||
*/
|
||||
private static windowsOnly(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
if (process.platform !== 'win32') {
|
||||
descriptor.value = function () {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!attributes.playParams || attributes.playParams.id === "no-id-found") {
|
||||
this._win.setThumbarButtons([]);
|
||||
} else {
|
||||
this._win.setThumbarButtons(buttons);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the thumbnail toolbar
|
||||
*/
|
||||
private updateButtons(attributes: any) {
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
console.log(attributes)
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(a: { getApp: () => any }) {
|
||||
this._app = utils.getApp();
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
}
|
||||
|
||||
if (!attributes) {
|
||||
return
|
||||
}
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
@Thumbar.windowsOnly
|
||||
onReady(win: Electron.BrowserWindow): void {
|
||||
this._win = win;
|
||||
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||
}
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
tooltip: 'Previous',
|
||||
icon: this.icons.previous,
|
||||
click() {
|
||||
utils.playback.previous()
|
||||
}
|
||||
},
|
||||
{
|
||||
tooltip: attributes.status ? 'Pause' : 'Play',
|
||||
icon: attributes.status ? this.icons.pause : this.icons.play,
|
||||
click() {
|
||||
utils.playback.playPause()
|
||||
}
|
||||
},
|
||||
{
|
||||
tooltip: 'Next',
|
||||
icon: this.icons.next,
|
||||
click() {
|
||||
utils.playback.next()
|
||||
}
|
||||
}
|
||||
];
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
@Thumbar.windowsOnly
|
||||
onBeforeQuit(): void {
|
||||
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||
}
|
||||
|
||||
if (!attributes.playParams || attributes.playParams.id === 'no-id-found') {
|
||||
this._win.setThumbarButtons([])
|
||||
} else {
|
||||
this._win.setThumbarButtons(buttons);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************************
|
||||
* Public Methods
|
||||
* ****************************************************************************************/
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(a: { getApp: () => any; }) {
|
||||
this._app = utils.getApp();
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
@Thumbar.windowsOnly
|
||||
onReady(win: Electron.BrowserWindow): void {
|
||||
this._win = win;
|
||||
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
@Thumbar.windowsOnly
|
||||
onBeforeQuit(): void {
|
||||
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
@Thumbar.windowsOnly
|
||||
onPlaybackStateDidChange(attributes: object): void {
|
||||
this.updateButtons(attributes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
@Thumbar.windowsOnly
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
this.updateButtons(attributes)
|
||||
}
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
@Thumbar.windowsOnly
|
||||
onPlaybackStateDidChange(attributes: object): void {
|
||||
this.updateButtons(attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
@Thumbar.windowsOnly
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
this.updateButtons(attributes);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as WebSocket from 'ws';
|
||||
import * as WebSocket from "ws";
|
||||
|
||||
/**
|
||||
* 0-pad a number.
|
||||
|
@ -6,7 +6,7 @@ import * as WebSocket from 'ws';
|
|||
* @param {Number} length
|
||||
* @returns String
|
||||
*/
|
||||
const pad = (number: number, length: number) => String(number).padStart(length, '0');
|
||||
const pad = (number: number, length: number) => String(number).padStart(length, "0");
|
||||
|
||||
/**
|
||||
* Convert seconds to a time string acceptable to Rainmeter
|
||||
|
@ -15,230 +15,230 @@ const pad = (number: number, length: number) => String(number).padStart(length,
|
|||
* @returns String
|
||||
*/
|
||||
const convertTimeToString = (timeInSeconds: number) => {
|
||||
const timeInMinutes = Math.floor(timeInSeconds / 60);
|
||||
if (timeInMinutes < 60) {
|
||||
return timeInMinutes + ":" + pad(Math.floor(timeInSeconds % 60), 2);
|
||||
}
|
||||
return Math.floor(timeInMinutes / 60) + ":" + pad(Math.floor(timeInMinutes % 60), 2) + ":" + pad(Math.floor(timeInSeconds % 60), 2);
|
||||
}
|
||||
const timeInMinutes = Math.floor(timeInSeconds / 60);
|
||||
if (timeInMinutes < 60) {
|
||||
return timeInMinutes + ":" + pad(Math.floor(timeInSeconds % 60), 2);
|
||||
}
|
||||
return Math.floor(timeInMinutes / 60) + ":" + pad(Math.floor(timeInMinutes % 60), 2) + ":" + pad(Math.floor(timeInSeconds % 60), 2);
|
||||
};
|
||||
|
||||
export default class WebNowPlaying {
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = 'WebNowPlaying';
|
||||
public description: string = 'Song info and playback control for the Rainmeter WebNowPlaying plugin.';
|
||||
public version: string = '1.0.1';
|
||||
public author: string = 'Zennn <me@jozen.blue>';
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = "WebNowPlaying";
|
||||
public description: string = "Song info and playback control for the Rainmeter WebNowPlaying plugin.";
|
||||
public version: string = "1.0.1";
|
||||
public author: string = "Zennn <me@jozen.blue>";
|
||||
|
||||
private _win: any;
|
||||
private ws?: WebSocket;
|
||||
private wsapiConn?: WebSocket;
|
||||
private playerName: string = 'Cider';
|
||||
private _win: any;
|
||||
private ws?: WebSocket;
|
||||
private wsapiConn?: WebSocket;
|
||||
private playerName: string = "Cider";
|
||||
|
||||
constructor() {
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
constructor() {
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks non-windows systems from running this plugin
|
||||
* @private
|
||||
* @decorator
|
||||
*/
|
||||
private static windowsOnly(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
if (process.platform !== "win32") {
|
||||
descriptor.value = () => void 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks non-windows systems from running this plugin
|
||||
* @private
|
||||
* @decorator
|
||||
*/
|
||||
private static windowsOnly(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
if (process.platform !== 'win32') {
|
||||
descriptor.value = () => void 0;
|
||||
private sendSongInfo(attributes: any) {
|
||||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
||||
|
||||
const fields = ["STATE", "TITLE", "ARTIST", "ALBUM", "COVER", "DURATION", "POSITION", "VOLUME", "REPEAT", "SHUFFLE"];
|
||||
fields.forEach((field) => {
|
||||
try {
|
||||
let value: any = "";
|
||||
switch (field) {
|
||||
case "STATE":
|
||||
value = attributes.status ? 1 : 2;
|
||||
break;
|
||||
case "TITLE":
|
||||
value = attributes.name;
|
||||
break;
|
||||
case "ARTIST":
|
||||
value = attributes.artistName;
|
||||
break;
|
||||
case "ALBUM":
|
||||
value = attributes.albumName;
|
||||
break;
|
||||
case "COVER":
|
||||
value = attributes.artwork.url.replace("{w}", attributes.artwork.width).replace("{h}", attributes.artwork.height);
|
||||
break;
|
||||
case "DURATION":
|
||||
value = convertTimeToString(attributes.durationInMillis / 1000);
|
||||
break;
|
||||
case "POSITION":
|
||||
value = convertTimeToString((attributes.durationInMillis - attributes.remainingTime) / 1000);
|
||||
break;
|
||||
case "VOLUME":
|
||||
value = attributes.volume * 100;
|
||||
break;
|
||||
case "REPEAT":
|
||||
value = attributes.repeatMode;
|
||||
break;
|
||||
case "SHUFFLE":
|
||||
value = attributes.shuffleMode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private sendSongInfo(attributes: any) {
|
||||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
||||
|
||||
const fields = ['STATE', 'TITLE', 'ARTIST', 'ALBUM', 'COVER', 'DURATION', 'POSITION', 'VOLUME', 'REPEAT', 'SHUFFLE'];
|
||||
fields.forEach((field) => {
|
||||
try {
|
||||
let value: any = '';
|
||||
switch (field) {
|
||||
case 'STATE':
|
||||
value = attributes.status ? 1 : 2;
|
||||
break;
|
||||
case 'TITLE':
|
||||
value = attributes.name;
|
||||
break;
|
||||
case 'ARTIST':
|
||||
value = attributes.artistName;
|
||||
break;
|
||||
case 'ALBUM':
|
||||
value = attributes.albumName;
|
||||
break;
|
||||
case 'COVER':
|
||||
value = attributes.artwork.url.replace('{w}', attributes.artwork.width).replace('{h}', attributes.artwork.height);
|
||||
break;
|
||||
case 'DURATION':
|
||||
value = convertTimeToString(attributes.durationInMillis / 1000);
|
||||
break;
|
||||
case 'POSITION':
|
||||
value = convertTimeToString((attributes.durationInMillis - attributes.remainingTime) / 1000);
|
||||
break;
|
||||
case 'VOLUME':
|
||||
value = attributes.volume * 100;
|
||||
break;
|
||||
case 'REPEAT':
|
||||
value = attributes.repeatMode;
|
||||
break;
|
||||
case 'SHUFFLE':
|
||||
value = attributes.shuffleMode;
|
||||
break;
|
||||
}
|
||||
this.ws?.send(`${field}:${value}`);
|
||||
} catch (error) {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(`Error:Error updating ${field} for ${this.playerName}`);
|
||||
this.ws.send(`ErrorD:${error}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private fireEvent(evt: WebSocket.MessageEvent) {
|
||||
if (!evt.data) return;
|
||||
const data = <string>evt.data;
|
||||
|
||||
let value: string = '';
|
||||
if (data.split(/ (.+)/).length > 1) {
|
||||
value = data.split(/ (.+)/)[1];
|
||||
this.ws?.send(`${field}:${value}`);
|
||||
} catch (error) {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(`Error:Error updating ${field} for ${this.playerName}`);
|
||||
this.ws.send(`ErrorD:${error}`);
|
||||
}
|
||||
const eventName = data.split(' ')[0].toLowerCase();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
switch (eventName) {
|
||||
case 'playpause':
|
||||
this._win.webContents.executeJavaScript('MusicKitInterop.playPause()').catch(console.error);
|
||||
break;
|
||||
case 'next':
|
||||
this._win.webContents.executeJavaScript('MusicKitInterop.next()').catch(console.error);
|
||||
break;
|
||||
case 'previous':
|
||||
this._win.webContents.executeJavaScript('MusicKitInterop.previous()').catch(console.error);
|
||||
break;
|
||||
case 'setposition':
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${parseFloat(value)})`);
|
||||
break;
|
||||
case 'setvolume':
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().volume = ${parseFloat(value) / 100}`);
|
||||
break;
|
||||
case 'repeat':
|
||||
this._win.webContents.executeJavaScript('wsapi.toggleRepeat()').catch(console.error);
|
||||
break;
|
||||
case 'shuffle':
|
||||
this._win.webContents.executeJavaScript('wsapi.toggleShuffle()').catch(console.error);
|
||||
break;
|
||||
case 'togglethumbsup':
|
||||
// not implemented
|
||||
break;
|
||||
case 'togglethumbsdown':
|
||||
// not implemented
|
||||
break;
|
||||
case 'rating':
|
||||
// not implemented
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(`Error:Error sending event to ${this.playerName}`);
|
||||
this.ws.send(`ErrorD:${error}`);
|
||||
}
|
||||
}
|
||||
private fireEvent(evt: WebSocket.MessageEvent) {
|
||||
if (!evt.data) return;
|
||||
const data = <string>evt.data;
|
||||
|
||||
let value: string = "";
|
||||
if (data.split(/ (.+)/).length > 1) {
|
||||
value = data.split(/ (.+)/)[1];
|
||||
}
|
||||
const eventName = data.split(" ")[0].toLowerCase();
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
@WebNowPlaying.windowsOnly
|
||||
public onReady(win: any) {
|
||||
this._win = win;
|
||||
try {
|
||||
switch (eventName) {
|
||||
case "playpause":
|
||||
this._win.webContents.executeJavaScript("MusicKitInterop.playPause()").catch(console.error);
|
||||
break;
|
||||
case "next":
|
||||
this._win.webContents.executeJavaScript("MusicKitInterop.next()").catch(console.error);
|
||||
break;
|
||||
case "previous":
|
||||
this._win.webContents.executeJavaScript("MusicKitInterop.previous()").catch(console.error);
|
||||
break;
|
||||
case "setposition":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${parseFloat(value)})`);
|
||||
break;
|
||||
case "setvolume":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().volume = ${parseFloat(value) / 100}`);
|
||||
break;
|
||||
case "repeat":
|
||||
this._win.webContents.executeJavaScript("wsapi.toggleRepeat()").catch(console.error);
|
||||
break;
|
||||
case "shuffle":
|
||||
this._win.webContents.executeJavaScript("wsapi.toggleShuffle()").catch(console.error);
|
||||
break;
|
||||
case "togglethumbsup":
|
||||
// not implemented
|
||||
break;
|
||||
case "togglethumbsdown":
|
||||
// not implemented
|
||||
break;
|
||||
case "rating":
|
||||
// not implemented
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(`Error:Error sending event to ${this.playerName}`);
|
||||
this.ws.send(`ErrorD:${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to Rainmeter plugin and retry on disconnect.
|
||||
const init = () => {
|
||||
try {
|
||||
this.ws = new WebSocket('ws://127.0.0.1:8974/');
|
||||
let retry: NodeJS.Timeout;
|
||||
this.ws.onopen = () => {
|
||||
console.info('[WebNowPlaying] Connected to Rainmeter');
|
||||
this.ws?.send(`PLAYER:${this.playerName}`);
|
||||
};
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
@WebNowPlaying.windowsOnly
|
||||
public onReady(win: any) {
|
||||
this._win = win;
|
||||
|
||||
this.ws.onclose = () => {
|
||||
clearTimeout(retry);
|
||||
retry = setTimeout(init, 2000);
|
||||
};
|
||||
|
||||
this.ws.onerror = () => {
|
||||
clearTimeout(retry);
|
||||
this.ws?.close();
|
||||
};
|
||||
|
||||
this.ws.onmessage = this.fireEvent?.bind(this);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
// Connect to Rainmeter plugin and retry on disconnect.
|
||||
const init = () => {
|
||||
try {
|
||||
this.ws = new WebSocket("ws://127.0.0.1:8974/");
|
||||
let retry: NodeJS.Timeout;
|
||||
this.ws.onopen = () => {
|
||||
console.info("[WebNowPlaying] Connected to Rainmeter");
|
||||
this.ws?.send(`PLAYER:${this.playerName}`);
|
||||
};
|
||||
|
||||
init();
|
||||
this.ws.onclose = () => {
|
||||
clearTimeout(retry);
|
||||
retry = setTimeout(init, 2000);
|
||||
};
|
||||
|
||||
// Connect to wsapi. Only used to update progress.
|
||||
try {
|
||||
this.wsapiConn = new WebSocket('ws://127.0.0.1:26369/');
|
||||
this.ws.onerror = () => {
|
||||
clearTimeout(retry);
|
||||
this.ws?.close();
|
||||
};
|
||||
|
||||
this.wsapiConn.onopen = () => {
|
||||
console.info('[WebNowPlaying] Connected to wsapi');
|
||||
};
|
||||
this.ws.onmessage = this.fireEvent?.bind(this);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
this.wsapiConn.onmessage = (evt: WebSocket.MessageEvent) => {
|
||||
const response = JSON.parse(<string>evt.data);
|
||||
if (response.type === 'playbackStateUpdate') {
|
||||
this.sendSongInfo(response.data);
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
init();
|
||||
|
||||
// Connect to wsapi. Only used to update progress.
|
||||
try {
|
||||
this.wsapiConn = new WebSocket("ws://127.0.0.1:26369/");
|
||||
|
||||
this.wsapiConn.onopen = () => {
|
||||
console.info("[WebNowPlaying] Connected to wsapi");
|
||||
};
|
||||
|
||||
this.wsapiConn.onmessage = (evt: WebSocket.MessageEvent) => {
|
||||
const response = JSON.parse(<string>evt.data);
|
||||
if (response.type === "playbackStateUpdate") {
|
||||
this.sendSongInfo(response.data);
|
||||
}
|
||||
|
||||
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
@WebNowPlaying.windowsOnly
|
||||
public onBeforeQuit() {
|
||||
if (this.ws) {
|
||||
this.ws.send('STATE:0');
|
||||
this.ws.onclose = () => void 0; // disable onclose handler first to stop it from retrying
|
||||
this.ws.close();
|
||||
}
|
||||
if (this.wsapiConn) {
|
||||
this.wsapiConn.close();
|
||||
}
|
||||
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||
}
|
||||
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
@WebNowPlaying.windowsOnly
|
||||
public onPlaybackStateDidChange(attributes: any) {
|
||||
this.sendSongInfo(attributes);
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
@WebNowPlaying.windowsOnly
|
||||
public onBeforeQuit() {
|
||||
if (this.ws) {
|
||||
this.ws.send("STATE:0");
|
||||
this.ws.onclose = () => void 0; // disable onclose handler first to stop it from retrying
|
||||
this.ws.close();
|
||||
}
|
||||
if (this.wsapiConn) {
|
||||
this.wsapiConn.close();
|
||||
}
|
||||
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
@WebNowPlaying.windowsOnly
|
||||
public onNowPlayingItemDidChange(attributes: any) {
|
||||
this.sendSongInfo(attributes);
|
||||
}
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
@WebNowPlaying.windowsOnly
|
||||
public onPlaybackStateDidChange(attributes: any) {
|
||||
this.sendSongInfo(attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
@WebNowPlaying.windowsOnly
|
||||
public onNowPlayingItemDidChange(attributes: any) {
|
||||
this.sendSongInfo(attributes);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue