CHONKY BOY
This commit is contained in:
parent
31ed921a1a
commit
c15f55d0ee
213 changed files with 64188 additions and 55736 deletions
|
@ -1 +1,2 @@
|
|||
bracketSameLine: true
|
||||
printWidth: 240
|
||||
|
|
|
@ -1,62 +1,62 @@
|
|||
let i = 1, k = 1;
|
||||
let i = 1,
|
||||
k = 1;
|
||||
class ExamplePlugin {
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private _win: any;
|
||||
private _app: any;
|
||||
private _store: any;
|
||||
/**
|
||||
* Private variables for interaction in plugins
|
||||
*/
|
||||
private _win: any;
|
||||
private _app: any;
|
||||
private _store: any;
|
||||
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = 'examplePlugin';
|
||||
public description: string = 'Example plugin';
|
||||
public version: string = '1.0.0';
|
||||
public author: string = 'Example author';
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = "examplePlugin";
|
||||
public description: string = "Example plugin";
|
||||
public version: string = "1.0.0";
|
||||
public author: string = "Example author";
|
||||
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(app: any, store: any) {
|
||||
this._app = app;
|
||||
this._store = store;
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
}
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
constructor(app: any, store: any) {
|
||||
this._app = app;
|
||||
this._store = store;
|
||||
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(win: any): void {
|
||||
this._win = win;
|
||||
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||
}
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(win: any): void {
|
||||
this._win = win;
|
||||
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {
|
||||
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||
}
|
||||
/**
|
||||
* 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 {
|
||||
console.log('onPlaybackStateDidChange has been called ' + i + ' times');
|
||||
i++
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
console.log('onNowPlayingDidChange has been called ' + k + ' times');
|
||||
k++
|
||||
}
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: object): void {
|
||||
console.log("onPlaybackStateDidChange has been called " + i + " times");
|
||||
i++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: object): void {
|
||||
console.log("onNowPlayingDidChange has been called " + k + " times");
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ExamplePlugin;
|
||||
module.exports = ExamplePlugin;
|
||||
|
|
|
@ -1,39 +1,39 @@
|
|||
class sendSongToTitlebar {
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = 'sendSongToTitlebar';
|
||||
public description: string = 'Sets the app\'s titlebar to the Song title';
|
||||
public version: string = '0.0.1';
|
||||
public author: string = 'Cider Collective (credit to 8times9 via #147)';
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
private _win: any;
|
||||
private _app: any;
|
||||
constructor() {}
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(win: any): void {
|
||||
this._win = win;
|
||||
}
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {}
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: any): void {
|
||||
this._win.setTitle(`${(attributes != null && attributes.name != null && attributes.name.length > 0) ? (attributes.name + " - ") : ''}Cider`)
|
||||
}
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: object): void {}
|
||||
/**
|
||||
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||
*/
|
||||
public name: string = "sendSongToTitlebar";
|
||||
public description: string = "Sets the app's titlebar to the Song title";
|
||||
public version: string = "0.0.1";
|
||||
public author: string = "Cider Collective (credit to 8times9 via #147)";
|
||||
/**
|
||||
* Runs on plugin load (Currently run on application start)
|
||||
*/
|
||||
private _win: any;
|
||||
private _app: any;
|
||||
constructor() {}
|
||||
/**
|
||||
* Runs on app ready
|
||||
*/
|
||||
onReady(win: any): void {
|
||||
this._win = win;
|
||||
}
|
||||
/**
|
||||
* Runs on app stop
|
||||
*/
|
||||
onBeforeQuit(): void {}
|
||||
/**
|
||||
* Runs on playback State Change
|
||||
* @param attributes Music Attributes (attributes.status = current state)
|
||||
*/
|
||||
onPlaybackStateDidChange(attributes: any): void {
|
||||
this._win.setTitle(`${attributes != null && attributes.name != null && attributes.name.length > 0 ? attributes.name + " - " : ""}Cider`);
|
||||
}
|
||||
/**
|
||||
* Runs on song change
|
||||
* @param attributes Music Attributes
|
||||
*/
|
||||
onNowPlayingItemDidChange(attributes: object): void {}
|
||||
}
|
||||
|
||||
module.exports = sendSongToTitlebar;
|
||||
module.exports = sendSongToTitlebar;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"only-arches": ["x86_64"],
|
||||
"publish-delay-hours": 2
|
||||
}
|
||||
"only-arches": ["x86_64"],
|
||||
"publish-delay-hours": 2
|
||||
}
|
||||
|
|
|
@ -6,31 +6,15 @@
|
|||
},
|
||||
"appId": "cider",
|
||||
"protocols": [
|
||||
{
|
||||
"name": "Cider",
|
||||
"schemes": [
|
||||
"ame",
|
||||
"cider",
|
||||
"itms",
|
||||
"itmss",
|
||||
"musics",
|
||||
"music"
|
||||
]
|
||||
{
|
||||
"name": "Cider",
|
||||
"schemes": ["ame", "cider", "itms", "itmss", "musics", "music"]
|
||||
}
|
||||
],
|
||||
"extends": null,
|
||||
"files": [
|
||||
"**/*",
|
||||
"./src/**/*",
|
||||
"./resources/icons/icon.*"
|
||||
],
|
||||
"files": ["**/*", "./src/**/*", "./resources/icons/icon.*"],
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage",
|
||||
"deb",
|
||||
"snap",
|
||||
"rpm"
|
||||
],
|
||||
"target": ["AppImage", "deb", "snap", "rpm"],
|
||||
"synopsis": "A new look into listening and enjoying music in style and performance. ",
|
||||
"category": "AudioVideo",
|
||||
"icon": "cider",
|
||||
|
@ -45,9 +29,7 @@
|
|||
"setBuildNumber": true
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"appx"
|
||||
],
|
||||
"target": ["appx"],
|
||||
"icon": "resources/icons/icon.ico"
|
||||
},
|
||||
"directories": {
|
||||
|
@ -60,4 +42,4 @@
|
|||
"entitlements": "resources/entitlements.mac.plist",
|
||||
"darkModeSupport": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
35
package.json
35
package.json
|
@ -35,51 +35,48 @@
|
|||
"msft": "yarn build && electron-builder -c msft-package.json",
|
||||
"mstest": "yarn build && electron-builder -c msft-test.json",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"prettier": "npx prettier --write '**/*.{js,json,ts}'"
|
||||
"prettier": "npx prettier --write '**/*.{js,json,ts,css,less}'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/electron": "^3.0.7",
|
||||
"@sentry/integrations": "^6.19.6",
|
||||
"@sentry/integrations": "^7.8.1",
|
||||
"@types/pouchdb": "^6.4.0",
|
||||
"@types/pouchdb-node": "^6.1.4",
|
||||
"adm-zip": "0.4.10",
|
||||
"airtunes2": "git+https://github.com/ciderapp/node_airtunes2.git",
|
||||
"castv2-client": "^1.2.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"discord-auto-rpc": "^1.0.16",
|
||||
"discord-auto-rpc": "^1.0.17",
|
||||
"dns-js": "git+https://github.com/ciderapp/node-dns-js.git",
|
||||
"ejs": "^3.1.6",
|
||||
"ejs": "^3.1.8",
|
||||
"electron-fetch": "^1.7.4",
|
||||
"electron-log": "^4.4.6",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-notarize": "^1.2.1",
|
||||
"electron-store": "^8.0.1",
|
||||
"electron-updater": "^5.0.1",
|
||||
"electron-store": "^8.1.0",
|
||||
"electron-updater": "^5.2.1",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"express": "^4.17.3",
|
||||
"express": "^4.18.1",
|
||||
"get-port": "^5.1.1",
|
||||
"jimp": "^0.16.1",
|
||||
"jsonc": "^2.0.0",
|
||||
"lastfmapi": "^0.1.1",
|
||||
"level": "^8.0.0",
|
||||
"leveldown": "^6.1.1",
|
||||
"mdns-js": "git+https://github.com/ciderapp/node-mdns-js.git",
|
||||
"mpris-service": "^2.1.2",
|
||||
"music-metadata": "^7.12.4",
|
||||
"node-gyp": "^9.0.0",
|
||||
"music-metadata": "^7.12.5",
|
||||
"node-gyp": "^9.1.0",
|
||||
"node-ssdp": "^4.0.1",
|
||||
"pouchdb-adapter-leveldb": "^7.3.0",
|
||||
"pouchdb-node": "^7.3.0",
|
||||
"pouchdb-upsert": "^2.2.0",
|
||||
"qrcode": "^1.5.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"run-script-os": "^1.1.6",
|
||||
"qrcode": "^1.5.1",
|
||||
"request": "^2.88.2",
|
||||
"run-script-os": "^1.1.6",
|
||||
"source-map-support": "^0.5.21",
|
||||
"ts-md5": "^1.2.11",
|
||||
"v8-compile-cache": "^2.3.0",
|
||||
"wallpaper": "5.0.1",
|
||||
"ws": "^8.5.0",
|
||||
"ws": "^8.8.1",
|
||||
"xml2js": "^0.4.23",
|
||||
"youtube-search-without-api-key": "^1.0.7"
|
||||
},
|
||||
|
@ -90,14 +87,14 @@
|
|||
"@types/qrcode-terminal": "^0.12.0",
|
||||
"@types/ws": "^8.5.3",
|
||||
"electron": "git+https://github.com/castlabs/electron-releases.git#18-x-y",
|
||||
"electron-builder": "^23.0.3",
|
||||
"electron-builder": "^23.3.3",
|
||||
"electron-builder-notarize-pkg": "^1.2.0",
|
||||
"electron-webpack": "^2.8.2",
|
||||
"less": "^4.1.3",
|
||||
"musickit-typescript": "^1.2.4",
|
||||
"typescript": "^4.6.4",
|
||||
"typescript": "^4.7.4",
|
||||
"vue-devtools": "^5.1.4",
|
||||
"webpack": "~5.72.0"
|
||||
"webpack": "~5.74.0"
|
||||
},
|
||||
"fileAssociations": [
|
||||
{
|
||||
|
|
|
@ -1,30 +1,35 @@
|
|||
exports.default = function(context) {
|
||||
const { execSync } = require('child_process')
|
||||
const fs = require('fs')
|
||||
exports.default = function (context) {
|
||||
const { execSync } = require("child_process");
|
||||
const fs = require("fs");
|
||||
|
||||
if (process.platform !== 'darwin')
|
||||
return
|
||||
|
||||
if (fs.existsSync('dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig'))
|
||||
fs.unlinkSync('dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig')
|
||||
if (fs.existsSync('dist/mac-universal--arm64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig'))
|
||||
fs.unlinkSync('dist/mac-universal--arm64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig')
|
||||
// console.log('Castlabs-evs update start')
|
||||
// execSync('python3 -m pip install --upgrade castlabs-evs')
|
||||
// console.log('Castlabs-evs update complete')
|
||||
if (process.platform !== "darwin") return;
|
||||
|
||||
// xcode 13
|
||||
if (fs.existsSync('dist/mac-universal--x64') && fs.existsSync('dist/mac-universal--arm64') && fs.existsSync('dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib/keyedobjects-101300.nib'))
|
||||
execSync("cp 'dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib/keyedobjects-101300.nib' 'dist/mac-universal--arm64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib/keyedobjects-101300.nib'",{stdio: 'inherit'})
|
||||
if (fs.existsSync("dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig"))
|
||||
fs.unlinkSync("dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig");
|
||||
if (fs.existsSync("dist/mac-universal--arm64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig"))
|
||||
fs.unlinkSync("dist/mac-universal--arm64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig");
|
||||
// console.log('Castlabs-evs update start')
|
||||
// execSync('python3 -m pip install --upgrade castlabs-evs')
|
||||
// console.log('Castlabs-evs update complete')
|
||||
|
||||
// console.log('VMP signing start')
|
||||
// if (fs.existsSync('dist/mac-universal'))
|
||||
// execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac-universal',{stdio: 'inherit'})
|
||||
// if (fs.existsSync('dist/mac'))
|
||||
// execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac',{stdio: 'inherit'})
|
||||
// if (fs.existsSync('dist/mac-arm64'))
|
||||
// execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac-arm64 -z',{stdio: 'inherit'})
|
||||
// xcode 13
|
||||
if (
|
||||
fs.existsSync("dist/mac-universal--x64") &&
|
||||
fs.existsSync("dist/mac-universal--arm64") &&
|
||||
fs.existsSync("dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib/keyedobjects-101300.nib")
|
||||
)
|
||||
execSync(
|
||||
"cp 'dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib/keyedobjects-101300.nib' 'dist/mac-universal--arm64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib/keyedobjects-101300.nib'",
|
||||
{ stdio: "inherit" }
|
||||
);
|
||||
|
||||
// console.log('VMP signing complete')
|
||||
// console.log('VMP signing start')
|
||||
// if (fs.existsSync('dist/mac-universal'))
|
||||
// execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac-universal',{stdio: 'inherit'})
|
||||
// if (fs.existsSync('dist/mac'))
|
||||
// execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac',{stdio: 'inherit'})
|
||||
// if (fs.existsSync('dist/mac-arm64'))
|
||||
// execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac-arm64 -z',{stdio: 'inherit'})
|
||||
|
||||
}
|
||||
// console.log('VMP signing complete')
|
||||
};
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
{
|
||||
"$schema": "https://github.com/argv-minus-one/dmg-license/raw/master/schema.json",
|
||||
"body": [
|
||||
{
|
||||
"file": "license.txt",
|
||||
"lang": ["en-US"]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
}
|
||||
"$schema": "https://github.com/argv-minus-one/dmg-license/raw/master/schema.json",
|
||||
"body": [
|
||||
{
|
||||
"file": "license.txt",
|
||||
"lang": ["en-US"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -19,381 +19,390 @@ const macosVersion_1 = require("./util/macosVersion");
|
|||
const pathManager_1 = require("./util/pathManager");
|
||||
const fs = require("fs/promises");
|
||||
class MacPackager extends platformPackager_1.PlatformPackager {
|
||||
constructor(info) {
|
||||
super(info, core_1.Platform.MAC);
|
||||
this.codeSigningInfo = new lazy_val_1.Lazy(() => {
|
||||
const cscLink = this.getCscLink();
|
||||
if (cscLink == null || process.platform !== "darwin") {
|
||||
return Promise.resolve({ keychainFile: process.env.CSC_KEYCHAIN || null });
|
||||
}
|
||||
return macCodeSign_1.createKeychain({
|
||||
tmpDir: this.info.tempDirManager,
|
||||
cscLink,
|
||||
cscKeyPassword: this.getCscPassword(),
|
||||
cscILink: platformPackager_1.chooseNotNull(this.platformSpecificBuildOptions.cscInstallerLink, process.env.CSC_INSTALLER_LINK),
|
||||
cscIKeyPassword: platformPackager_1.chooseNotNull(this.platformSpecificBuildOptions.cscInstallerKeyPassword, process.env.CSC_INSTALLER_KEY_PASSWORD),
|
||||
currentDir: this.projectDir,
|
||||
}).then(result => {
|
||||
const keychainFile = result.keychainFile;
|
||||
if (keychainFile != null) {
|
||||
this.info.disposeOnBuildFinish(() => macCodeSign_1.removeKeychain(keychainFile));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
constructor(info) {
|
||||
super(info, core_1.Platform.MAC);
|
||||
this.codeSigningInfo = new lazy_val_1.Lazy(() => {
|
||||
const cscLink = this.getCscLink();
|
||||
if (cscLink == null || process.platform !== "darwin") {
|
||||
return Promise.resolve({
|
||||
keychainFile: process.env.CSC_KEYCHAIN || null,
|
||||
});
|
||||
this._iconPath = new lazy_val_1.Lazy(() => this.getOrConvertIcon("icns"));
|
||||
}
|
||||
return macCodeSign_1
|
||||
.createKeychain({
|
||||
tmpDir: this.info.tempDirManager,
|
||||
cscLink,
|
||||
cscKeyPassword: this.getCscPassword(),
|
||||
cscILink: platformPackager_1.chooseNotNull(this.platformSpecificBuildOptions.cscInstallerLink, process.env.CSC_INSTALLER_LINK),
|
||||
cscIKeyPassword: platformPackager_1.chooseNotNull(this.platformSpecificBuildOptions.cscInstallerKeyPassword, process.env.CSC_INSTALLER_KEY_PASSWORD),
|
||||
currentDir: this.projectDir,
|
||||
})
|
||||
.then((result) => {
|
||||
const keychainFile = result.keychainFile;
|
||||
if (keychainFile != null) {
|
||||
this.info.disposeOnBuildFinish(() => macCodeSign_1.removeKeychain(keychainFile));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
});
|
||||
this._iconPath = new lazy_val_1.Lazy(() => this.getOrConvertIcon("icns"));
|
||||
}
|
||||
get defaultTarget() {
|
||||
return this.info.framework.macOsDefaultTargets;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
prepareAppInfo(appInfo) {
|
||||
return new appInfo_1.AppInfo(this.info, this.platformSpecificBuildOptions.bundleVersion, this.platformSpecificBuildOptions);
|
||||
}
|
||||
async getIconPath() {
|
||||
return this._iconPath.value;
|
||||
}
|
||||
createTargets(targets, mapper) {
|
||||
for (const name of targets) {
|
||||
switch (name) {
|
||||
case core_1.DIR_TARGET:
|
||||
break;
|
||||
case "dmg": {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { DmgTarget } = require("dmg-builder");
|
||||
mapper(name, (outDir) => new DmgTarget(this, outDir));
|
||||
break;
|
||||
}
|
||||
case "zip":
|
||||
// https://github.com/electron-userland/electron-builder/issues/2313
|
||||
mapper(name, (outDir) => new ArchiveTarget_1.ArchiveTarget(name, outDir, this, true));
|
||||
break;
|
||||
case "pkg":
|
||||
mapper(name, (outDir) => new pkg_1.PkgTarget(this, outDir));
|
||||
break;
|
||||
default:
|
||||
mapper(name, (outDir) => (name === "mas" || name === "mas-dev" ? new targetFactory_1.NoOpTarget(name) : targetFactory_1.createCommonTarget(name, outDir, this)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
get defaultTarget() {
|
||||
return this.info.framework.macOsDefaultTargets;
|
||||
}
|
||||
async doPack(outDir, appOutDir, platformName, arch, platformSpecificBuildOptions, targets) {
|
||||
switch (arch) {
|
||||
default: {
|
||||
return super.doPack(outDir, appOutDir, platformName, arch, platformSpecificBuildOptions, targets);
|
||||
}
|
||||
case builder_util_1.Arch.universal: {
|
||||
const x64Arch = builder_util_1.Arch.x64;
|
||||
const x64AppOutDir = appOutDir + "--" + builder_util_1.Arch[x64Arch];
|
||||
await super.doPack(outDir, x64AppOutDir, platformName, x64Arch, platformSpecificBuildOptions, targets, false, true);
|
||||
const arm64Arch = builder_util_1.Arch.arm64;
|
||||
const arm64AppOutPath = appOutDir + "--" + builder_util_1.Arch[arm64Arch];
|
||||
await super.doPack(outDir, arm64AppOutPath, platformName, arm64Arch, platformSpecificBuildOptions, targets, false, true);
|
||||
const framework = this.info.framework;
|
||||
builder_util_1.log.info(
|
||||
{
|
||||
platform: platformName,
|
||||
arch: builder_util_1.Arch[arch],
|
||||
[`${framework.name}`]: framework.version,
|
||||
appOutDir: builder_util_1.log.filePath(appOutDir),
|
||||
},
|
||||
`packaging`
|
||||
);
|
||||
const appFile = `${this.appInfo.productFilename}.app`;
|
||||
const { makeUniversalApp } = require("@electron/universal");
|
||||
await makeUniversalApp({
|
||||
x64AppPath: path.join(x64AppOutDir, appFile),
|
||||
arm64AppPath: path.join(arm64AppOutPath, appFile),
|
||||
outAppPath: path.join(appOutDir, appFile),
|
||||
force: true,
|
||||
});
|
||||
await fs.rm(x64AppOutDir, { recursive: true, force: true });
|
||||
await fs.rm(arm64AppOutPath, { recursive: true, force: true });
|
||||
const packContext = {
|
||||
appOutDir,
|
||||
outDir,
|
||||
arch,
|
||||
targets,
|
||||
packager: this,
|
||||
electronPlatformName: platformName,
|
||||
};
|
||||
await this.info.afterPack(packContext);
|
||||
if (framework.afterPack != null) {
|
||||
await framework.afterPack(packContext);
|
||||
}
|
||||
await this.doSignAfterPack(outDir, appOutDir, platformName, arch, platformSpecificBuildOptions, targets);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
prepareAppInfo(appInfo) {
|
||||
return new appInfo_1.AppInfo(this.info, this.platformSpecificBuildOptions.bundleVersion, this.platformSpecificBuildOptions);
|
||||
}
|
||||
async pack(outDir, arch, targets, taskManager) {
|
||||
let nonMasPromise = null;
|
||||
const hasMas = targets.length !== 0 && targets.some((it) => it.name === "mas" || it.name === "mas-dev");
|
||||
const prepackaged = this.packagerOptions.prepackaged;
|
||||
if (!hasMas || targets.length > 1) {
|
||||
const appPath = prepackaged == null ? path.join(this.computeAppOutDir(outDir, arch), `${this.appInfo.productFilename}.app`) : prepackaged;
|
||||
nonMasPromise = (prepackaged ? Promise.resolve() : this.doPack(outDir, path.dirname(appPath), this.platform.nodeName, arch, this.platformSpecificBuildOptions, targets)).then(() =>
|
||||
this.packageInDistributableFormat(appPath, arch, targets, taskManager)
|
||||
);
|
||||
}
|
||||
async getIconPath() {
|
||||
return this._iconPath.value;
|
||||
for (const target of targets) {
|
||||
const targetName = target.name;
|
||||
if (!(targetName === "mas" || targetName === "mas-dev")) {
|
||||
continue;
|
||||
}
|
||||
const masBuildOptions = builder_util_1.deepAssign({}, this.platformSpecificBuildOptions, this.config.mas);
|
||||
if (targetName === "mas-dev") {
|
||||
builder_util_1.deepAssign(masBuildOptions, this.config.masDev, {
|
||||
type: "development",
|
||||
});
|
||||
}
|
||||
const targetOutDir = path.join(outDir, `${targetName}${builder_util_1.getArchSuffix(arch)}`);
|
||||
if (prepackaged == null) {
|
||||
await this.doPack(outDir, targetOutDir, "mas", arch, masBuildOptions, [target]);
|
||||
await this.sign(path.join(targetOutDir, `${this.appInfo.productFilename}.app`), targetOutDir, masBuildOptions, arch);
|
||||
} else {
|
||||
await this.sign(prepackaged, targetOutDir, masBuildOptions, arch);
|
||||
}
|
||||
}
|
||||
createTargets(targets, mapper) {
|
||||
for (const name of targets) {
|
||||
switch (name) {
|
||||
case core_1.DIR_TARGET:
|
||||
break;
|
||||
case "dmg": {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { DmgTarget } = require("dmg-builder");
|
||||
mapper(name, outDir => new DmgTarget(this, outDir));
|
||||
break;
|
||||
}
|
||||
case "zip":
|
||||
// https://github.com/electron-userland/electron-builder/issues/2313
|
||||
mapper(name, outDir => new ArchiveTarget_1.ArchiveTarget(name, outDir, this, true));
|
||||
break;
|
||||
case "pkg":
|
||||
mapper(name, outDir => new pkg_1.PkgTarget(this, outDir));
|
||||
break;
|
||||
default:
|
||||
mapper(name, outDir => (name === "mas" || name === "mas-dev" ? new targetFactory_1.NoOpTarget(name) : targetFactory_1.createCommonTarget(name, outDir, this)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nonMasPromise != null) {
|
||||
await nonMasPromise;
|
||||
}
|
||||
async doPack(outDir, appOutDir, platformName, arch, platformSpecificBuildOptions, targets) {
|
||||
switch (arch) {
|
||||
default: {
|
||||
return super.doPack(outDir, appOutDir, platformName, arch, platformSpecificBuildOptions, targets);
|
||||
}
|
||||
case builder_util_1.Arch.universal: {
|
||||
const x64Arch = builder_util_1.Arch.x64;
|
||||
const x64AppOutDir = appOutDir + "--" + builder_util_1.Arch[x64Arch];
|
||||
await super.doPack(outDir, x64AppOutDir, platformName, x64Arch, platformSpecificBuildOptions, targets, false, true);
|
||||
const arm64Arch = builder_util_1.Arch.arm64;
|
||||
const arm64AppOutPath = appOutDir + "--" + builder_util_1.Arch[arm64Arch];
|
||||
await super.doPack(outDir, arm64AppOutPath, platformName, arm64Arch, platformSpecificBuildOptions, targets, false, true);
|
||||
const framework = this.info.framework;
|
||||
builder_util_1.log.info({
|
||||
platform: platformName,
|
||||
arch: builder_util_1.Arch[arch],
|
||||
[`${framework.name}`]: framework.version,
|
||||
appOutDir: builder_util_1.log.filePath(appOutDir),
|
||||
}, `packaging`);
|
||||
const appFile = `${this.appInfo.productFilename}.app`;
|
||||
const { makeUniversalApp } = require("@electron/universal");
|
||||
await makeUniversalApp({
|
||||
x64AppPath: path.join(x64AppOutDir, appFile),
|
||||
arm64AppPath: path.join(arm64AppOutPath, appFile),
|
||||
outAppPath: path.join(appOutDir, appFile),
|
||||
force: true,
|
||||
});
|
||||
await fs.rm(x64AppOutDir, { recursive: true, force: true });
|
||||
await fs.rm(arm64AppOutPath, { recursive: true, force: true });
|
||||
const packContext = {
|
||||
appOutDir,
|
||||
outDir,
|
||||
arch,
|
||||
targets,
|
||||
packager: this,
|
||||
electronPlatformName: platformName,
|
||||
}
|
||||
await this.info.afterPack(packContext)
|
||||
if (framework.afterPack != null) {
|
||||
await framework.afterPack(packContext)
|
||||
}
|
||||
await this.doSignAfterPack(outDir, appOutDir, platformName, arch, platformSpecificBuildOptions, targets);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
async sign(appPath, outDir, masOptions, arch) {
|
||||
if (!macCodeSign_1.isSignAllowed()) {
|
||||
return;
|
||||
}
|
||||
async pack(outDir, arch, targets, taskManager) {
|
||||
let nonMasPromise = null;
|
||||
const hasMas = targets.length !== 0 && targets.some(it => it.name === "mas" || it.name === "mas-dev");
|
||||
const prepackaged = this.packagerOptions.prepackaged;
|
||||
if (!hasMas || targets.length > 1) {
|
||||
const appPath = prepackaged == null ? path.join(this.computeAppOutDir(outDir, arch), `${this.appInfo.productFilename}.app`) : prepackaged;
|
||||
nonMasPromise = (prepackaged
|
||||
? Promise.resolve()
|
||||
: this.doPack(outDir, path.dirname(appPath), this.platform.nodeName, arch, this.platformSpecificBuildOptions, targets)).then(() => this.packageInDistributableFormat(appPath, arch, targets, taskManager));
|
||||
}
|
||||
for (const target of targets) {
|
||||
const targetName = target.name;
|
||||
if (!(targetName === "mas" || targetName === "mas-dev")) {
|
||||
continue;
|
||||
}
|
||||
const masBuildOptions = builder_util_1.deepAssign({}, this.platformSpecificBuildOptions, this.config.mas);
|
||||
if (targetName === "mas-dev") {
|
||||
builder_util_1.deepAssign(masBuildOptions, this.config.masDev, {
|
||||
type: "development",
|
||||
});
|
||||
}
|
||||
const targetOutDir = path.join(outDir, `${targetName}${builder_util_1.getArchSuffix(arch)}`);
|
||||
if (prepackaged == null) {
|
||||
await this.doPack(outDir, targetOutDir, "mas", arch, masBuildOptions, [target]);
|
||||
await this.sign(path.join(targetOutDir, `${this.appInfo.productFilename}.app`), targetOutDir, masBuildOptions, arch);
|
||||
}
|
||||
else {
|
||||
await this.sign(prepackaged, targetOutDir, masBuildOptions, arch);
|
||||
}
|
||||
}
|
||||
if (nonMasPromise != null) {
|
||||
await nonMasPromise;
|
||||
}
|
||||
const isMas = masOptions != null;
|
||||
const options = masOptions == null ? this.platformSpecificBuildOptions : masOptions;
|
||||
const qualifier = options.identity;
|
||||
if (!isMas && qualifier === null) {
|
||||
if (this.forceCodeSigning) {
|
||||
throw new builder_util_1.InvalidConfigurationError("identity explicitly is set to null, but forceCodeSigning is set to true");
|
||||
}
|
||||
builder_util_1.log.info({ reason: "identity explicitly is set to null" }, "skipped macOS code signing");
|
||||
return;
|
||||
}
|
||||
async sign(appPath, outDir, masOptions, arch) {
|
||||
if (!macCodeSign_1.isSignAllowed()) {
|
||||
return;
|
||||
const keychainFile = (await this.codeSigningInfo.value).keychainFile;
|
||||
const explicitType = options.type;
|
||||
const type = explicitType || "distribution";
|
||||
const isDevelopment = type === "development";
|
||||
const certificateTypes = getCertificateTypes(isMas, isDevelopment);
|
||||
let identity = null;
|
||||
for (const certificateType of certificateTypes) {
|
||||
identity = await macCodeSign_1.findIdentity(certificateType, qualifier, keychainFile);
|
||||
if (identity != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (identity == null) {
|
||||
if (!isMas && !isDevelopment && explicitType !== "distribution") {
|
||||
identity = await macCodeSign_1.findIdentity("Mac Developer", qualifier, keychainFile);
|
||||
if (identity != null) {
|
||||
builder_util_1.log.warn("Mac Developer is used to sign app — it is only for development and testing, not for production");
|
||||
}
|
||||
const isMas = masOptions != null;
|
||||
const options = masOptions == null ? this.platformSpecificBuildOptions : masOptions;
|
||||
const qualifier = options.identity;
|
||||
if (!isMas && qualifier === null) {
|
||||
if (this.forceCodeSigning) {
|
||||
throw new builder_util_1.InvalidConfigurationError("identity explicitly is set to null, but forceCodeSigning is set to true");
|
||||
}
|
||||
if (identity == null) {
|
||||
await macCodeSign_1.reportError(isMas, certificateTypes, qualifier, keychainFile, this.forceCodeSigning);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!macosVersion_1.isMacOsHighSierra()) {
|
||||
throw new builder_util_1.InvalidConfigurationError("macOS High Sierra 10.13.6 is required to sign");
|
||||
}
|
||||
let filter = options.signIgnore;
|
||||
if (Array.isArray(filter)) {
|
||||
if (filter.length == 0) {
|
||||
filter = null;
|
||||
}
|
||||
} else if (filter != null) {
|
||||
filter = filter.length === 0 ? null : [filter];
|
||||
}
|
||||
const filterRe = filter == null ? null : filter.map((it) => new RegExp(it));
|
||||
let binaries = options.binaries || undefined;
|
||||
if (binaries) {
|
||||
// Accept absolute paths for external binaries, else resolve relative paths from the artifact's app Contents path.
|
||||
const userDefinedBinaries = await Promise.all(
|
||||
binaries.map(async (destination) => {
|
||||
if (await fs_1.statOrNull(destination)) {
|
||||
return destination;
|
||||
}
|
||||
return path.resolve(appPath, destination);
|
||||
})
|
||||
);
|
||||
// Insert at front to prioritize signing. We still sort by depth next
|
||||
binaries = userDefinedBinaries.concat(binaries);
|
||||
builder_util_1.log.info("Signing addtional user-defined binaries: " + JSON.stringify(userDefinedBinaries, null, 1));
|
||||
}
|
||||
const signOptions = {
|
||||
"identity-validation": false,
|
||||
// https://github.com/electron-userland/electron-builder/issues/1699
|
||||
// kext are signed by the chipset manufacturers. You need a special certificate (only available on request) from Apple to be able to sign kext.
|
||||
ignore: (file) => {
|
||||
if (filterRe != null) {
|
||||
for (const regExp of filterRe) {
|
||||
if (regExp.test(file)) {
|
||||
return true;
|
||||
}
|
||||
builder_util_1.log.info({ reason: "identity explicitly is set to null" }, "skipped macOS code signing");
|
||||
return;
|
||||
}
|
||||
}
|
||||
const keychainFile = (await this.codeSigningInfo.value).keychainFile;
|
||||
const explicitType = options.type;
|
||||
const type = explicitType || "distribution";
|
||||
const isDevelopment = type === "development";
|
||||
const certificateTypes = getCertificateTypes(isMas, isDevelopment);
|
||||
let identity = null;
|
||||
for (const certificateType of certificateTypes) {
|
||||
identity = await macCodeSign_1.findIdentity(certificateType, qualifier, keychainFile);
|
||||
if (identity != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (identity == null) {
|
||||
if (!isMas && !isDevelopment && explicitType !== "distribution") {
|
||||
identity = await macCodeSign_1.findIdentity("Mac Developer", qualifier, keychainFile);
|
||||
if (identity != null) {
|
||||
builder_util_1.log.warn("Mac Developer is used to sign app — it is only for development and testing, not for production");
|
||||
}
|
||||
}
|
||||
if (identity == null) {
|
||||
await macCodeSign_1.reportError(isMas, certificateTypes, qualifier, keychainFile, this.forceCodeSigning);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!macosVersion_1.isMacOsHighSierra()) {
|
||||
throw new builder_util_1.InvalidConfigurationError("macOS High Sierra 10.13.6 is required to sign");
|
||||
}
|
||||
let filter = options.signIgnore;
|
||||
if (Array.isArray(filter)) {
|
||||
if (filter.length == 0) {
|
||||
filter = null;
|
||||
}
|
||||
}
|
||||
else if (filter != null) {
|
||||
filter = filter.length === 0 ? null : [filter];
|
||||
}
|
||||
const filterRe = filter == null ? null : filter.map(it => new RegExp(it));
|
||||
let binaries = options.binaries || undefined;
|
||||
if (binaries) {
|
||||
// Accept absolute paths for external binaries, else resolve relative paths from the artifact's app Contents path.
|
||||
const userDefinedBinaries = await Promise.all(binaries.map(async (destination) => {
|
||||
if (await fs_1.statOrNull(destination)) {
|
||||
return destination;
|
||||
}
|
||||
return path.resolve(appPath, destination);
|
||||
}));
|
||||
// Insert at front to prioritize signing. We still sort by depth next
|
||||
binaries = userDefinedBinaries.concat(binaries);
|
||||
builder_util_1.log.info("Signing addtional user-defined binaries: " + JSON.stringify(userDefinedBinaries, null, 1));
|
||||
}
|
||||
const signOptions = {
|
||||
"identity-validation": false,
|
||||
// https://github.com/electron-userland/electron-builder/issues/1699
|
||||
// kext are signed by the chipset manufacturers. You need a special certificate (only available on request) from Apple to be able to sign kext.
|
||||
ignore: (file) => {
|
||||
if (filterRe != null) {
|
||||
for (const regExp of filterRe) {
|
||||
if (regExp.test(file)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (file.endsWith(".kext") ||
|
||||
file.startsWith("/Contents/PlugIns", appPath.length) ||
|
||||
file.includes("/node_modules/puppeteer/.local-chromium") ||
|
||||
file.includes("/node_modules/playwright-firefox/.local-browsers") ||
|
||||
file.includes("/node_modules/playwright/.local-browsers"));
|
||||
/* Those are browser automating modules, browser (chromium, nightly) cannot be signed
|
||||
return (
|
||||
file.endsWith(".kext") ||
|
||||
file.startsWith("/Contents/PlugIns", appPath.length) ||
|
||||
file.includes("/node_modules/puppeteer/.local-chromium") ||
|
||||
file.includes("/node_modules/playwright-firefox/.local-browsers") ||
|
||||
file.includes("/node_modules/playwright/.local-browsers")
|
||||
);
|
||||
/* Those are browser automating modules, browser (chromium, nightly) cannot be signed
|
||||
https://github.com/electron-userland/electron-builder/issues/2010
|
||||
https://github.com/electron-userland/electron-builder/issues/5383
|
||||
*/
|
||||
},
|
||||
identity: identity,
|
||||
type,
|
||||
platform: isMas ? "mas" : "darwin",
|
||||
version: this.config.electronVersion,
|
||||
app: appPath,
|
||||
keychain: keychainFile || undefined,
|
||||
binaries,
|
||||
timestamp: isMas ? masOptions === null || masOptions === void 0 ? void 0 : masOptions.timestamp : options.timestamp,
|
||||
requirements: isMas || this.platformSpecificBuildOptions.requirements == null ? undefined : await this.getResource(this.platformSpecificBuildOptions.requirements),
|
||||
// https://github.com/electron-userland/electron-osx-sign/issues/196
|
||||
// will fail on 10.14.5+ because a signed but unnotarized app is also rejected.
|
||||
"gatekeeper-assess": options.gatekeeperAssess === true,
|
||||
// https://github.com/electron-userland/electron-builder/issues/1480
|
||||
"strict-verify": options.strictVerify,
|
||||
hardenedRuntime: isMas ? masOptions && masOptions.hardenedRuntime === true : options.hardenedRuntime !== false,
|
||||
};
|
||||
await this.adjustSignOptions(signOptions, masOptions);
|
||||
builder_util_1.log.info({
|
||||
file: builder_util_1.log.filePath(appPath),
|
||||
identityName: identity.name,
|
||||
identityHash: identity.hash,
|
||||
provisioningProfile: signOptions["provisioning-profile"] || "none",
|
||||
}, "signing");
|
||||
await this.doSign(signOptions);
|
||||
// https://github.com/electron-userland/electron-builder/issues/1196#issuecomment-312310209
|
||||
if (masOptions != null && !isDevelopment) {
|
||||
const certType = isDevelopment ? "Mac Developer" : "3rd Party Mac Developer Installer";
|
||||
const masInstallerIdentity = await macCodeSign_1.findIdentity(certType, masOptions.identity, keychainFile);
|
||||
if (masInstallerIdentity == null) {
|
||||
throw new builder_util_1.InvalidConfigurationError(`Cannot find valid "${certType}" identity to sign MAS installer, please see https://electron.build/code-signing`);
|
||||
}
|
||||
// mas uploaded to AppStore, so, use "-" instead of space for name
|
||||
const artifactName = this.expandArtifactNamePattern(masOptions, "pkg", arch);
|
||||
const artifactPath = path.join(outDir, artifactName);
|
||||
await this.doFlat(appPath, artifactPath, masInstallerIdentity, keychainFile);
|
||||
await this.dispatchArtifactCreated(artifactPath, null, builder_util_1.Arch.x64, this.computeSafeArtifactName(artifactName, "pkg", arch, true, this.platformSpecificBuildOptions.defaultArch));
|
||||
}
|
||||
},
|
||||
identity: identity,
|
||||
type,
|
||||
platform: isMas ? "mas" : "darwin",
|
||||
version: this.config.electronVersion,
|
||||
app: appPath,
|
||||
keychain: keychainFile || undefined,
|
||||
binaries,
|
||||
timestamp: isMas ? (masOptions === null || masOptions === void 0 ? void 0 : masOptions.timestamp) : options.timestamp,
|
||||
requirements: isMas || this.platformSpecificBuildOptions.requirements == null ? undefined : await this.getResource(this.platformSpecificBuildOptions.requirements),
|
||||
// https://github.com/electron-userland/electron-osx-sign/issues/196
|
||||
// will fail on 10.14.5+ because a signed but unnotarized app is also rejected.
|
||||
"gatekeeper-assess": options.gatekeeperAssess === true,
|
||||
// https://github.com/electron-userland/electron-builder/issues/1480
|
||||
"strict-verify": options.strictVerify,
|
||||
hardenedRuntime: isMas ? masOptions && masOptions.hardenedRuntime === true : options.hardenedRuntime !== false,
|
||||
};
|
||||
await this.adjustSignOptions(signOptions, masOptions);
|
||||
builder_util_1.log.info(
|
||||
{
|
||||
file: builder_util_1.log.filePath(appPath),
|
||||
identityName: identity.name,
|
||||
identityHash: identity.hash,
|
||||
provisioningProfile: signOptions["provisioning-profile"] || "none",
|
||||
},
|
||||
"signing"
|
||||
);
|
||||
await this.doSign(signOptions);
|
||||
// https://github.com/electron-userland/electron-builder/issues/1196#issuecomment-312310209
|
||||
if (masOptions != null && !isDevelopment) {
|
||||
const certType = isDevelopment ? "Mac Developer" : "3rd Party Mac Developer Installer";
|
||||
const masInstallerIdentity = await macCodeSign_1.findIdentity(certType, masOptions.identity, keychainFile);
|
||||
if (masInstallerIdentity == null) {
|
||||
throw new builder_util_1.InvalidConfigurationError(`Cannot find valid "${certType}" identity to sign MAS installer, please see https://electron.build/code-signing`);
|
||||
}
|
||||
// mas uploaded to AppStore, so, use "-" instead of space for name
|
||||
const artifactName = this.expandArtifactNamePattern(masOptions, "pkg", arch);
|
||||
const artifactPath = path.join(outDir, artifactName);
|
||||
await this.doFlat(appPath, artifactPath, masInstallerIdentity, keychainFile);
|
||||
await this.dispatchArtifactCreated(artifactPath, null, builder_util_1.Arch.x64, this.computeSafeArtifactName(artifactName, "pkg", arch, true, this.platformSpecificBuildOptions.defaultArch));
|
||||
}
|
||||
async adjustSignOptions(signOptions, masOptions) {
|
||||
const resourceList = await this.resourceList;
|
||||
const customSignOptions = masOptions || this.platformSpecificBuildOptions;
|
||||
const entitlementsSuffix = masOptions == null ? "mac" : "mas";
|
||||
let entitlements = customSignOptions.entitlements;
|
||||
if (entitlements == null) {
|
||||
const p = `entitlements.${entitlementsSuffix}.plist`;
|
||||
if (resourceList.includes(p)) {
|
||||
entitlements = path.join(this.info.buildResourcesDir, p);
|
||||
}
|
||||
else {
|
||||
entitlements = pathManager_1.getTemplatePath("entitlements.mac.plist");
|
||||
}
|
||||
}
|
||||
signOptions.entitlements = entitlements;
|
||||
let entitlementsInherit = customSignOptions.entitlementsInherit;
|
||||
if (entitlementsInherit == null) {
|
||||
const p = `entitlements.${entitlementsSuffix}.inherit.plist`;
|
||||
if (resourceList.includes(p)) {
|
||||
entitlementsInherit = path.join(this.info.buildResourcesDir, p);
|
||||
}
|
||||
else {
|
||||
entitlementsInherit = pathManager_1.getTemplatePath("entitlements.mac.plist");
|
||||
}
|
||||
}
|
||||
signOptions["entitlements-inherit"] = entitlementsInherit;
|
||||
if (customSignOptions.provisioningProfile != null) {
|
||||
signOptions["provisioning-profile"] = customSignOptions.provisioningProfile;
|
||||
}
|
||||
signOptions["entitlements-loginhelper"] = customSignOptions.entitlementsLoginHelper;
|
||||
}
|
||||
async adjustSignOptions(signOptions, masOptions) {
|
||||
const resourceList = await this.resourceList;
|
||||
const customSignOptions = masOptions || this.platformSpecificBuildOptions;
|
||||
const entitlementsSuffix = masOptions == null ? "mac" : "mas";
|
||||
let entitlements = customSignOptions.entitlements;
|
||||
if (entitlements == null) {
|
||||
const p = `entitlements.${entitlementsSuffix}.plist`;
|
||||
if (resourceList.includes(p)) {
|
||||
entitlements = path.join(this.info.buildResourcesDir, p);
|
||||
} else {
|
||||
entitlements = pathManager_1.getTemplatePath("entitlements.mac.plist");
|
||||
}
|
||||
}
|
||||
//noinspection JSMethodCanBeStatic
|
||||
async doSign(opts) {
|
||||
return electron_osx_sign_1.signAsync(opts);
|
||||
signOptions.entitlements = entitlements;
|
||||
let entitlementsInherit = customSignOptions.entitlementsInherit;
|
||||
if (entitlementsInherit == null) {
|
||||
const p = `entitlements.${entitlementsSuffix}.inherit.plist`;
|
||||
if (resourceList.includes(p)) {
|
||||
entitlementsInherit = path.join(this.info.buildResourcesDir, p);
|
||||
} else {
|
||||
entitlementsInherit = pathManager_1.getTemplatePath("entitlements.mac.plist");
|
||||
}
|
||||
}
|
||||
//noinspection JSMethodCanBeStatic
|
||||
async doFlat(appPath, outFile, identity, keychain) {
|
||||
// productbuild doesn't created directory for out file
|
||||
await promises_1.mkdir(path.dirname(outFile), { recursive: true });
|
||||
const args = pkg_1.prepareProductBuildArgs(identity, keychain);
|
||||
args.push("--component", appPath, "/Applications");
|
||||
args.push(outFile);
|
||||
return await builder_util_1.exec("productbuild", args);
|
||||
signOptions["entitlements-inherit"] = entitlementsInherit;
|
||||
if (customSignOptions.provisioningProfile != null) {
|
||||
signOptions["provisioning-profile"] = customSignOptions.provisioningProfile;
|
||||
}
|
||||
getElectronSrcDir(dist) {
|
||||
return path.resolve(this.projectDir, dist, this.info.framework.distMacOsAppName);
|
||||
signOptions["entitlements-loginhelper"] = customSignOptions.entitlementsLoginHelper;
|
||||
}
|
||||
//noinspection JSMethodCanBeStatic
|
||||
async doSign(opts) {
|
||||
return electron_osx_sign_1.signAsync(opts);
|
||||
}
|
||||
//noinspection JSMethodCanBeStatic
|
||||
async doFlat(appPath, outFile, identity, keychain) {
|
||||
// productbuild doesn't created directory for out file
|
||||
await promises_1.mkdir(path.dirname(outFile), { recursive: true });
|
||||
const args = pkg_1.prepareProductBuildArgs(identity, keychain);
|
||||
args.push("--component", appPath, "/Applications");
|
||||
args.push(outFile);
|
||||
return await builder_util_1.exec("productbuild", args);
|
||||
}
|
||||
getElectronSrcDir(dist) {
|
||||
return path.resolve(this.projectDir, dist, this.info.framework.distMacOsAppName);
|
||||
}
|
||||
getElectronDestinationDir(appOutDir) {
|
||||
return path.join(appOutDir, this.info.framework.distMacOsAppName);
|
||||
}
|
||||
// todo fileAssociations
|
||||
async applyCommonInfo(appPlist, contentsPath) {
|
||||
const appInfo = this.appInfo;
|
||||
const appFilename = appInfo.productFilename;
|
||||
// https://github.com/electron-userland/electron-builder/issues/1278
|
||||
appPlist.CFBundleExecutable = appFilename.endsWith(" Helper") ? appFilename.substring(0, appFilename.length - " Helper".length) : appFilename;
|
||||
const icon = await this.getIconPath();
|
||||
if (icon != null) {
|
||||
const oldIcon = appPlist.CFBundleIconFile;
|
||||
const resourcesPath = path.join(contentsPath, "Resources");
|
||||
if (oldIcon != null) {
|
||||
await fs_1.unlinkIfExists(path.join(resourcesPath, oldIcon));
|
||||
}
|
||||
const iconFileName = "icon.icns";
|
||||
appPlist.CFBundleIconFile = iconFileName;
|
||||
await fs_1.copyFile(icon, path.join(resourcesPath, iconFileName));
|
||||
}
|
||||
getElectronDestinationDir(appOutDir) {
|
||||
return path.join(appOutDir, this.info.framework.distMacOsAppName);
|
||||
appPlist.CFBundleName = appInfo.productName;
|
||||
appPlist.CFBundleDisplayName = appInfo.productName;
|
||||
const minimumSystemVersion = this.platformSpecificBuildOptions.minimumSystemVersion;
|
||||
if (minimumSystemVersion != null) {
|
||||
appPlist.LSMinimumSystemVersion = minimumSystemVersion;
|
||||
}
|
||||
// todo fileAssociations
|
||||
async applyCommonInfo(appPlist, contentsPath) {
|
||||
const appInfo = this.appInfo;
|
||||
const appFilename = appInfo.productFilename;
|
||||
// https://github.com/electron-userland/electron-builder/issues/1278
|
||||
appPlist.CFBundleExecutable = appFilename.endsWith(" Helper") ? appFilename.substring(0, appFilename.length - " Helper".length) : appFilename;
|
||||
const icon = await this.getIconPath();
|
||||
if (icon != null) {
|
||||
const oldIcon = appPlist.CFBundleIconFile;
|
||||
const resourcesPath = path.join(contentsPath, "Resources");
|
||||
if (oldIcon != null) {
|
||||
await fs_1.unlinkIfExists(path.join(resourcesPath, oldIcon));
|
||||
}
|
||||
const iconFileName = "icon.icns";
|
||||
appPlist.CFBundleIconFile = iconFileName;
|
||||
await fs_1.copyFile(icon, path.join(resourcesPath, iconFileName));
|
||||
}
|
||||
appPlist.CFBundleName = appInfo.productName;
|
||||
appPlist.CFBundleDisplayName = appInfo.productName;
|
||||
const minimumSystemVersion = this.platformSpecificBuildOptions.minimumSystemVersion;
|
||||
if (minimumSystemVersion != null) {
|
||||
appPlist.LSMinimumSystemVersion = minimumSystemVersion;
|
||||
}
|
||||
appPlist.CFBundleIdentifier = appInfo.macBundleIdentifier;
|
||||
appPlist.CFBundleShortVersionString = this.platformSpecificBuildOptions.bundleShortVersion || appInfo.version;
|
||||
appPlist.CFBundleVersion = appInfo.buildVersion;
|
||||
builder_util_1.use(this.platformSpecificBuildOptions.category || this.config.category, it => (appPlist.LSApplicationCategoryType = it));
|
||||
appPlist.NSHumanReadableCopyright = appInfo.copyright;
|
||||
if (this.platformSpecificBuildOptions.darkModeSupport) {
|
||||
appPlist.NSRequiresAquaSystemAppearance = false;
|
||||
}
|
||||
const extendInfo = this.platformSpecificBuildOptions.extendInfo;
|
||||
if (extendInfo != null) {
|
||||
Object.assign(appPlist, extendInfo);
|
||||
}
|
||||
appPlist.CFBundleIdentifier = appInfo.macBundleIdentifier;
|
||||
appPlist.CFBundleShortVersionString = this.platformSpecificBuildOptions.bundleShortVersion || appInfo.version;
|
||||
appPlist.CFBundleVersion = appInfo.buildVersion;
|
||||
builder_util_1.use(this.platformSpecificBuildOptions.category || this.config.category, (it) => (appPlist.LSApplicationCategoryType = it));
|
||||
appPlist.NSHumanReadableCopyright = appInfo.copyright;
|
||||
if (this.platformSpecificBuildOptions.darkModeSupport) {
|
||||
appPlist.NSRequiresAquaSystemAppearance = false;
|
||||
}
|
||||
async signApp(packContext, isAsar) {
|
||||
const appFileName = `${this.appInfo.productFilename}.app`;
|
||||
await bluebird_lst_1.default.map(promises_1.readdir(packContext.appOutDir), (file) => {
|
||||
if (file === appFileName) {
|
||||
return this.sign(path.join(packContext.appOutDir, file), null, null, null);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
if (!isAsar) {
|
||||
return;
|
||||
}
|
||||
const outResourcesDir = path.join(packContext.appOutDir, "resources", "app.asar.unpacked");
|
||||
await bluebird_lst_1.default.map(promise_1.orIfFileNotExist(promises_1.readdir(outResourcesDir), []), (file) => {
|
||||
if (file.endsWith(".app")) {
|
||||
return this.sign(path.join(outResourcesDir, file), null, null, null);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
const extendInfo = this.platformSpecificBuildOptions.extendInfo;
|
||||
if (extendInfo != null) {
|
||||
Object.assign(appPlist, extendInfo);
|
||||
}
|
||||
}
|
||||
async signApp(packContext, isAsar) {
|
||||
const appFileName = `${this.appInfo.productFilename}.app`;
|
||||
await bluebird_lst_1.default.map(promises_1.readdir(packContext.appOutDir), (file) => {
|
||||
if (file === appFileName) {
|
||||
return this.sign(path.join(packContext.appOutDir, file), null, null, null);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
if (!isAsar) {
|
||||
return;
|
||||
}
|
||||
const outResourcesDir = path.join(packContext.appOutDir, "resources", "app.asar.unpacked");
|
||||
await bluebird_lst_1.default.map(promise_1.orIfFileNotExist(promises_1.readdir(outResourcesDir), []), (file) => {
|
||||
if (file.endsWith(".app")) {
|
||||
return this.sign(path.join(outResourcesDir, file), null, null, null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.default = MacPackager;
|
||||
function getCertificateTypes(isMas, isDevelopment) {
|
||||
if (isDevelopment) {
|
||||
return isMas ? ["Mac Developer", "Apple Development"] : ["Developer ID Application"];
|
||||
}
|
||||
return isMas ? ["Apple Distribution"] : ["Developer ID Application"];
|
||||
if (isDevelopment) {
|
||||
return isMas ? ["Mac Developer", "Apple Development"] : ["Developer ID Application"];
|
||||
}
|
||||
return isMas ? ["Apple Distribution"] : ["Developer ID Application"];
|
||||
}
|
||||
//# sourceMappingURL=macPackager.js.map
|
||||
//# sourceMappingURL=macPackager.js.map
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
require("dotenv").config();
|
||||
const { notarize } = require("electron-notarize");
|
||||
|
||||
|
||||
require('dotenv').config();
|
||||
const { notarize } = require('electron-notarize');
|
||||
|
||||
exports.default = async function notarizing(context) {
|
||||
const { electronPlatformName, appOutDir } = context;
|
||||
if (electronPlatformName !== 'darwin') {
|
||||
return;
|
||||
}
|
||||
|
||||
const appName = context.packager.appInfo.productFilename;
|
||||
|
||||
return await notarize({
|
||||
appBundleId: 'com.ciderapp.cider',
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
appleId: process.env.APPLEID,
|
||||
appleIdPassword: process.env.APPLEIDPASS,
|
||||
});
|
||||
};
|
||||
const { electronPlatformName, appOutDir } = context;
|
||||
if (electronPlatformName !== "darwin") {
|
||||
return;
|
||||
}
|
||||
|
||||
const appName = context.packager.appInfo.productFilename;
|
||||
|
||||
return await notarize({
|
||||
appBundleId: "com.ciderapp.cider",
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
appleId: process.env.APPLEID,
|
||||
appleIdPassword: process.env.APPLEIDPASS,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
const CiderKit = {
|
||||
v1: {
|
||||
musickit: {
|
||||
async mkv3(route, body, options) {
|
||||
let opts = {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
redirect: 'follow',
|
||||
referrerPolicy: 'no-referrer',
|
||||
body: {}
|
||||
}
|
||||
opts.body = JSON.stringify({
|
||||
route: route,
|
||||
body: body,
|
||||
options: options
|
||||
})
|
||||
let response = await fetch("./api/musickit/v3", opts);
|
||||
return response.json()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
v1: {
|
||||
musickit: {
|
||||
async mkv3(route, body, options) {
|
||||
let opts = {
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
redirect: "follow",
|
||||
referrerPolicy: "no-referrer",
|
||||
body: {},
|
||||
};
|
||||
opts.body = JSON.stringify({
|
||||
route: route,
|
||||
body: body,
|
||||
options: options,
|
||||
});
|
||||
let response = await fetch("./api/musickit/v3", opts);
|
||||
return response.json();
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -303,10 +303,10 @@
|
|||
"settings.option.general.resumebehavior.locally.description": "Cider wird die letzte Sitzung auf diesem Rechner fortsetzen.",
|
||||
"settings.option.general.resumebehavior.history": "Verlauf",
|
||||
"settings.option.general.resumebehavior.history.description": "Cider wird den letzten Song aus dem geräteübergreifenden Apple-Music-Verlauf in die Warteschlange stellen.",
|
||||
"settings.option.general.resumetabs" : "Tab beim Start öffnen",
|
||||
"settings.option.general.resumetabs.description" : "Wähle welcher Tab beim Starten von Cider geöffnet werden soll.",
|
||||
"settings.option.general.resumetabs.dynamic" : "Dynamisch",
|
||||
"settings.option.general.resumetabs.dynamic.description" : "Cider wird den zuletzt genutzten Tab öffnen.",
|
||||
"settings.option.general.resumetabs": "Tab beim Start öffnen",
|
||||
"settings.option.general.resumetabs.description": "Wähle welcher Tab beim Starten von Cider geöffnet werden soll.",
|
||||
"settings.option.general.resumetabs.dynamic": "Dynamisch",
|
||||
"settings.option.general.resumetabs.dynamic.description": "Cider wird den zuletzt genutzten Tab öffnen.",
|
||||
"settings.option.general.keybindings": "Tastenkombinationen",
|
||||
"settings.option.general.keybindings.pressCombination": "Drücke eine Kombination aus zwei Tasten um die Tastenkombination zu aktualisieren.",
|
||||
"settings.option.general.keybindings.pressEscape": "Drücke Escape um zurückzukehren.",
|
||||
|
@ -396,7 +396,7 @@
|
|||
"action.selectAll": "Alles auswählen",
|
||||
"action.delete": "Löschen",
|
||||
"home.syncFavorites": "Sync Favoriten",
|
||||
"term.quit" : "Beenden",
|
||||
"term.quit": "Beenden",
|
||||
"settings.option.connectivity.lastfmScrobble.filterLoop.description": "Verhindert, dass geloopte Titel gescrobbelt oder in der (Hört Gerade)-Liste auf Last.fm angezeigt werden",
|
||||
"settings.option.connectivity.lastfmScrobble.filterTypes": "Medientypen filtern (Last.fm)",
|
||||
"settings.option.connectivity.lastfmScrobble.manualToken": "Last.fm-Token manuell eingeben"
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
"action.tray.minimize": "Minimise to Tray",
|
||||
"term.tracks": "songs",
|
||||
"term.track": {
|
||||
"one" : "song",
|
||||
"other" : "songs"
|
||||
"one": "song",
|
||||
"other": "songs"
|
||||
},
|
||||
"home.syncFavorites": "Sync Favourites",
|
||||
"home.syncFavorites.gettingArtists": "Getting Favourited Artists...",
|
||||
|
|
|
@ -642,4 +642,4 @@
|
|||
"oobe.visual.suggestingThemes.community3": "Dwacuwa",
|
||||
"oobe.visual.suggestingThemes.community3.text": "The iconyic Dwacuwa cowow scheme.",
|
||||
"oobe.amsignin.title": ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -202,16 +202,16 @@
|
|||
"term.confirmLogout": "Are you sure you want to logout?",
|
||||
"term.creditDesignedBy": "Designed by ${authorUsername}",
|
||||
"term.discNumber": "Disc ${discNumber}",
|
||||
"term.reload" : "Reload Cider?",
|
||||
"term.toggleprivate" : "Toggle Private Session",
|
||||
"term.webremote" : "Web Remote",
|
||||
"term.cast" : "Cast",
|
||||
"term.cast2" : "Cast to Devices",
|
||||
"term.quit" : "Quit",
|
||||
"term.zoomin" : "Zoom In",
|
||||
"term.zoomout" : "Zoom Out",
|
||||
"term.zoomreset" : "Reset Zoom",
|
||||
"term.fullscreen" : "Fullscreen",
|
||||
"term.reload": "Reload Cider?",
|
||||
"term.toggleprivate": "Toggle Private Session",
|
||||
"term.webremote": "Web Remote",
|
||||
"term.cast": "Cast",
|
||||
"term.cast2": "Cast to Devices",
|
||||
"term.quit": "Quit",
|
||||
"term.zoomin": "Zoom In",
|
||||
"term.zoomout": "Zoom Out",
|
||||
"term.zoomreset": "Reset Zoom",
|
||||
"term.fullscreen": "Fullscreen",
|
||||
"term.nowPlaying": "Now Playing",
|
||||
"home.syncFavorites": "Sync Favorites",
|
||||
"home.syncFavorites.gettingArtists": "Getting Favorited Artists...",
|
||||
|
@ -340,10 +340,10 @@
|
|||
"settings.option.general.resumebehavior.locally.description": "Cider will resume your last session on this machine.",
|
||||
"settings.option.general.resumebehavior.history": "History",
|
||||
"settings.option.general.resumebehavior.history.description": "Cider will queue the last song from your overall Apple Music history, across devices.",
|
||||
"settings.option.general.resumetabs" : "Open Tab on Launch",
|
||||
"settings.option.general.resumetabs.description" : "You can choose what tab you want to open when you launch Cider.",
|
||||
"settings.option.general.resumetabs.dynamic" : "Dynamic",
|
||||
"settings.option.general.resumetabs.dynamic.description" : "Cider will open the tab that you last used.",
|
||||
"settings.option.general.resumetabs": "Open Tab on Launch",
|
||||
"settings.option.general.resumetabs.description": "You can choose what tab you want to open when you launch Cider.",
|
||||
"settings.option.general.resumetabs.dynamic": "Dynamic",
|
||||
"settings.option.general.resumetabs.dynamic.description": "Cider will open the tab that you last used.",
|
||||
"settings.option.general.language.main": "Languages",
|
||||
"settings.option.general.language.fun": "Fun Languages",
|
||||
"settings.option.general.language.unsorted": "Unsorted",
|
||||
|
@ -403,7 +403,7 @@
|
|||
"settings.warn.audio.enableAdvancedFunctionality.lowcores": "Cider thinks your PC can't handle these features. Are you sure you want to continue?",
|
||||
"settings.option.audio.audioLab": "Cider Audio Lab",
|
||||
"settings.option.audio.audioLab.description": "An assortment of in-house developed audio effects for Cider.",
|
||||
"settings.option.audio.audioLab.subheader": "Designed by Cider Acoustic Technologies in California",
|
||||
"settings.option.audio.audioLab.subheader": "Designed by Cider Acoustic Technologies in California",
|
||||
"settings.warn.audioLab.withoutAF": "AudioContext (Advanced Functionality) is required to enable Cider Audio Laboratory.",
|
||||
"settings.warn.enableAdvancedFunctionality": "AudioContext (Advanced Functionality) is required to enable this feature.",
|
||||
"settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Analog Warmth",
|
||||
|
@ -645,4 +645,4 @@
|
|||
"oobe.visual.suggestingThemes.community3": "Dracula",
|
||||
"oobe.visual.suggestingThemes.community3.text": "The iconic Dracula color scheme.",
|
||||
"oobe.amsignin.title": ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -201,16 +201,16 @@
|
|||
"term.confirmLogout": "¿Estás seguro de que quieres cerrar sesión?",
|
||||
"term.creditDesignedBy": "Diseñado por ${authorUsername}",
|
||||
"term.discNumber": "Disco ${discNumber}",
|
||||
"term.reload" : "¿ Recargar Cider ?",
|
||||
"term.toggleprivate" : "Cambiar a Sesión Privada",
|
||||
"term.webremote" : "Web Remoto",
|
||||
"term.cast" : "Transmitir",
|
||||
"term.cast2" : "Transmitir a los Dispositivos",
|
||||
"term.quit" : "Salir",
|
||||
"term.zoomin" : "Acercar",
|
||||
"term.zoomout" : "Alejar",
|
||||
"term.zoomreset" : "Restablecer",
|
||||
"term.fullscreen" : "Pantalla Completa",
|
||||
"term.reload": "¿ Recargar Cider ?",
|
||||
"term.toggleprivate": "Cambiar a Sesión Privada",
|
||||
"term.webremote": "Web Remoto",
|
||||
"term.cast": "Transmitir",
|
||||
"term.cast2": "Transmitir a los Dispositivos",
|
||||
"term.quit": "Salir",
|
||||
"term.zoomin": "Acercar",
|
||||
"term.zoomout": "Alejar",
|
||||
"term.zoomreset": "Restablecer",
|
||||
"term.fullscreen": "Pantalla Completa",
|
||||
"term.nowPlaying": "Reproduciendo Ahora",
|
||||
"home.syncFavorites": "Sincronizar Favoritos",
|
||||
"home.syncFavorites.gettingArtists": "Consiguiendo Artistas Favoritos...",
|
||||
|
|
|
@ -201,16 +201,16 @@
|
|||
"term.confirmLogout": "¿Estás seguro de que quieres cerrar sesión?",
|
||||
"term.creditDesignedBy": "Diseñado por ${authorUsername}",
|
||||
"term.discNumber": "Disco ${discNumber}",
|
||||
"term.reload" : "¿ Recargar Cider ?",
|
||||
"term.toggleprivate" : "Cambiar a Sesión Privada",
|
||||
"term.webremote" : "Web Remoto",
|
||||
"term.cast" : "Transmitir",
|
||||
"term.cast2" : "Transmitir a los Dispositivos",
|
||||
"term.quit" : "Salir",
|
||||
"term.zoomin" : "Acercar",
|
||||
"term.zoomout" : "Alejar",
|
||||
"term.zoomreset" : "Restablecer",
|
||||
"term.fullscreen" : "Pantalla Completa",
|
||||
"term.reload": "¿ Recargar Cider ?",
|
||||
"term.toggleprivate": "Cambiar a Sesión Privada",
|
||||
"term.webremote": "Web Remoto",
|
||||
"term.cast": "Transmitir",
|
||||
"term.cast2": "Transmitir a los Dispositivos",
|
||||
"term.quit": "Salir",
|
||||
"term.zoomin": "Acercar",
|
||||
"term.zoomout": "Alejar",
|
||||
"term.zoomreset": "Restablecer",
|
||||
"term.fullscreen": "Pantalla Completa",
|
||||
"term.nowPlaying": "Reproduciendo Ahora",
|
||||
"home.syncFavorites": "Sincronizar Favoritos",
|
||||
"home.syncFavorites.gettingArtists": "Consiguiendo Artistas Favoritos...",
|
||||
|
|
|
@ -1,313 +1,312 @@
|
|||
{
|
||||
"i18n.languageName": "Suomi (FI)",
|
||||
"i18n.languageNameEnglish": "Finnish (FI)",
|
||||
"i18n.category": "main",
|
||||
"i18n.authors": "@marcusziade",
|
||||
"app.name": "Cider",
|
||||
"date.format": "${d} ${m}, ${y}",
|
||||
"dialog.cancel": "Peruuta",
|
||||
"dialog.ok": "OK",
|
||||
"notification.updatingLibrarySongs": "Päivitä kirjaston kappaleet...",
|
||||
"notification.updatingLibraryAlbums": "Päivitä kirjaston albumit...",
|
||||
"notification.updatingLibraryArtists": "Päivitä kirjaston artistit...",
|
||||
"term.appleInc": "Apple Inc.",
|
||||
"term.appleMusic": "Apple Music",
|
||||
"term.applePodcasts": "Apple Podcasts",
|
||||
"term.itunes": "iTunes",
|
||||
"term.github": "GitHub",
|
||||
"term.discord": "Discord",
|
||||
"term.learnMore": "Näytä lisää",
|
||||
"term.accountSettings": "Tilin asetukset",
|
||||
"term.logout": "Kirjaudu ulos",
|
||||
"term.login": "Kirjaudu sisään",
|
||||
"term.about": "Lisätietoja",
|
||||
"term.privateSession": "Yksityinen tila",
|
||||
"term.queue": "Jono",
|
||||
"term.search": "Etsi",
|
||||
"term.library": "Kirjasto",
|
||||
"term.listenNow": "Kuuntele nyt",
|
||||
"term.browse": "Selaa",
|
||||
"term.radio": "Radio",
|
||||
"term.recentlyAdded": "Viimeksi lisätyt",
|
||||
"term.songs": "Kappaleet",
|
||||
"term.albums": "Albumit",
|
||||
"term.artists": "Artistit",
|
||||
"term.podcasts": "Podcastit",
|
||||
"term.playlists": "Soittolistat",
|
||||
"term.playlist": "Soittolista",
|
||||
"term.newPlaylist": "Uusi soittolista",
|
||||
"term.newPlaylistFolder": "Uusi soittolistakansio",
|
||||
"term.createNewPlaylist": "Luo uusi soittolista",
|
||||
"term.createNewPlaylistFolder": "Luo uusi soittolistakansio",
|
||||
"term.deletePlaylist": "Oletko varma, että haluat poistaa tämän soittolistan?",
|
||||
"term.play": "Soita",
|
||||
"term.pause": "Tauko",
|
||||
"term.previous": "Edellinen",
|
||||
"term.next": "Seuraava",
|
||||
"term.shuffle": "Sekoita",
|
||||
"term.repeat": "Toista",
|
||||
"term.volume": "Volyymi",
|
||||
"term.mute": "Mykistä",
|
||||
"term.unmute": "Poista mykistys",
|
||||
"term.share": "Jaa",
|
||||
"term.settings": "Asetukset",
|
||||
"term.seeAll": "Näe kaikki",
|
||||
"term.sortBy": "Järjestä",
|
||||
"term.sortBy.album": "Järjestä albumin mukaan",
|
||||
"term.sortBy.artist": "Järjestä artistin mukaan",
|
||||
"term.sortBy.name": "Järjestä nimen mukaan",
|
||||
"term.sortBy.genre": "Järjestä genren mukaan",
|
||||
"term.sortBy.releaseDate": "Julkaisupäivä",
|
||||
"term.sortBy.duration": "Pituus",
|
||||
"term.sortOrder": "Järjestys",
|
||||
"term.sortOrder.ascending": "Nousevassa järjestyksessä",
|
||||
"term.sortOrder.descending": "Laskevassa järjestyksessä",
|
||||
"term.viewAs": "Näytä kuin",
|
||||
"term.viewAs.coverArt": "Kansikuva",
|
||||
"term.viewAs.list": "Lista",
|
||||
"term.size": "Koko",
|
||||
"term.size.normal": "Normaali",
|
||||
"term.size.compact": "Kompakti",
|
||||
"term.enable": "Ota käyttöön",
|
||||
"term.disable": "Poista käytöstä",
|
||||
"term.enabled": "Käytössä",
|
||||
"term.disabled": "Poissa käytöstä",
|
||||
"term.connect": "Yhdistä",
|
||||
"term.connecting": "Yhdistää",
|
||||
"term.disconnect": "Katkaise",
|
||||
"term.authed": "Tunnistettu",
|
||||
"term.confirm": "Vahvista",
|
||||
"term.more": "Lisää",
|
||||
"term.less": "Vähemmän",
|
||||
"term.showMore": "Näytä lisää",
|
||||
"term.showLess": "Näytä vähemmän",
|
||||
"term.topSongs": "Huippukappaleet",
|
||||
"term.latestReleases": "Viimeisimmät julkaisut",
|
||||
"term.time.added": "Lisätty",
|
||||
"term.time.released": "Julkaistu",
|
||||
"term.time.updated": "Päivitetty",
|
||||
"term.time.hours": "Tuntia",
|
||||
"term.time.hour": "Tunti",
|
||||
"term.time.minutes": "Minuuttiaa",
|
||||
"term.time.minute": "Minuutti",
|
||||
"term.time.seconds": "Sekuntia",
|
||||
"term.time.second": "Sekunti",
|
||||
"term.fullscreenView": "Koko näytön näkymä",
|
||||
"term.defaultView": "Oletusnäkymä",
|
||||
"term.audioSettings": "Ääniasetukset",
|
||||
"term.clearAll": "Puhdista kaikki",
|
||||
"term.recentStations": "Viimeisimmät asemat",
|
||||
"term.language": "Kieli",
|
||||
"term.funLanguages": "Hauskat kielet",
|
||||
"term.noLyrics": "Ei sanoituksia",
|
||||
"term.copyright": "Tekijänoikeus",
|
||||
"term.rightsReserved": "Oikeudet pidätetään",
|
||||
"term.sponsor": "Sponsoroi tätä projektia",
|
||||
"term.ciderTeam": "Cider tiimi",
|
||||
"term.developer": "Kehittäjä",
|
||||
"term.socialTeam": "Sosiaalinen tiimi",
|
||||
"term.socials": "Sosiaaliset mediat",
|
||||
"term.contributors": "Avustaja",
|
||||
"term.equalizer": "Taajuuskorjain",
|
||||
"term.reset": "Nollaa",
|
||||
"term.tracks": "Kappaleita",
|
||||
"term.videos": "Videoita",
|
||||
"term.menu": "Valikko",
|
||||
"term.check": "Tarkista",
|
||||
"term.aboutArtist": "Lisätiedot {{artistName}}",
|
||||
"home.title": "Koti",
|
||||
"home.recentlyPlayed": "Viimeksi soitetut",
|
||||
"home.recentlyAdded": "Viimeksi lisätyt",
|
||||
"home.artistsFeed": "Artisti syöte",
|
||||
"home.artistsFeed.noArtist": "Seuraa joitain artisteja nähdäksesi heidän uusimmat julkaisunsa.",
|
||||
"home.madeForYou": "Tehty sinulle",
|
||||
"home.friendsListeningTo": "Kaverit kuuntelee",
|
||||
"home.followedArtists": "Seuratut artistit",
|
||||
"error.appleMusicSubRequired": "Apple Music vaatii tilauksen.",
|
||||
"error.connectionError": "Apple Musiciin yhdistämisessä oli ongelma.",
|
||||
"error.noResults": "Ei tuloksia.",
|
||||
"error.noResults.description": "Kokeile uutta hakua.",
|
||||
"podcast.followOnCider": "Seuraa Ciderissa",
|
||||
"podcast.followedOnCider": "Seurattu Ciderissa",
|
||||
"podcast.subscribeOnItunes": "Tilaa iTunesissa",
|
||||
"podcast.subscribedOnItunes": "Tilattu iTunesissa",
|
||||
"podcast.itunesStore": "iTunes Store",
|
||||
"podcast.episodes": "Jakso",
|
||||
"podcast.playEpisode": "Toista jakso",
|
||||
"podcast.website": "Avaa nettisivu",
|
||||
"action.addToLibrary": "Lisää kirjastoon",
|
||||
"action.addToLibrary.success": "Lisätty kirjastoon",
|
||||
"action.addToLibrary.error": "Virhe lisättäessä kirjastoon",
|
||||
"action.removeFromLibrary": "Poista kirjastosta",
|
||||
"action.removeFromLibrary.success": "Poistettu kirjastosta",
|
||||
"action.addToQueue": "Lisää jonoon",
|
||||
"action.addToQueue.success": "Lisätty jonoon",
|
||||
"action.addToQueue.error": "Virhe lisättäessä jonoon",
|
||||
"action.removeFromQueue": "Poista jonosta",
|
||||
"action.removeFromQueue.success": "Poistettu jonosta",
|
||||
"action.removeFromQueue.error": "Virhe poistettaessa jonosta",
|
||||
"action.createPlaylist": "Luo uusi soittolista",
|
||||
"action.addToPlaylist": "Lisää soittolistaan",
|
||||
"action.removeFromPlaylist": "Poista soittolistasta",
|
||||
"action.addToFavorites": "Lisää suosikkeihin",
|
||||
"action.follow": "Seuraa",
|
||||
"action.follow.success": "Seurattu",
|
||||
"action.follow.error": "Virhe seurattaessa",
|
||||
"action.unfollow": "Lopeta seuraaminen",
|
||||
"action.unfollow.success": "Seuraaminen lopetettu",
|
||||
"action.unfollow.error": "Virhe seuraamisen lopetuksessa",
|
||||
"action.playNext": "Toista seuraavaksi",
|
||||
"action.playLater": "Toista myöhemmin",
|
||||
"action.startRadio": "Aloita radio",
|
||||
"action.goToArtist": "Näytä artisti",
|
||||
"action.goToAlbum": "Näytä albumi",
|
||||
"action.moveToTop": "Siirrä kärkeen",
|
||||
"action.share": "Jaa",
|
||||
"action.rename": "Nimeä uudelleen",
|
||||
"action.love": "Tykkää",
|
||||
"action.unlove": "Poista tykkäys",
|
||||
"action.dislike": "En tykkää",
|
||||
"action.undoDislike": "Kumoa en tykkää",
|
||||
"action.showWebRemoteQR": "Cider web kaukoohjain",
|
||||
"action.playTracksNext": "Toista ${app.selectedMediaItems.length} kappaleet seuraavaksi",
|
||||
"action.playTracksLater": "Toista ${app.selectedMediaItems.length} kappaleet myöhemmin",
|
||||
"action.removeTracks": "Poista ${self.selectedItems.length} kappaleet jonosta",
|
||||
"action.import": "Tuonti",
|
||||
"action.export": "Vienti",
|
||||
"action.showAlbum": "Näytä albumi",
|
||||
"action.tray.minimize": "Pienennä",
|
||||
"action.tray.quit": "Sammuta",
|
||||
"action.tray.show": "Näytä",
|
||||
"action.update": "Päivitä",
|
||||
"settings.header.general": "Yleistä",
|
||||
"settings.header.general.description": "Muuta yleisasetuksia",
|
||||
"settings.option.general.language": "Kieli",
|
||||
"settings.option.general.language.main": "Kieli",
|
||||
"settings.option.general.language.fun": "Hauskat kielet",
|
||||
"settings.option.general.language.unsorted": "Lajittelematon",
|
||||
"settings.header.audio": "Ääni",
|
||||
"settings.header.audio.description": "Muuta ääniasetuksia",
|
||||
"settings.option.audio.quality": "Äänenlaatu",
|
||||
"settings.header.audio.quality.high": "Korkea",
|
||||
"settings.option.audio.seamlessTransition": "Saumaton siirtyminen",
|
||||
"settings.option.audio.enableAdvancedFunctionality": "Ota lisätoiminnot käyttöön",
|
||||
"settings.option.audio.enableAdvancedFunctionality.description": "AudioContext-toiminnon ottaminen käyttöön mahdollistaa laajennetut ääniominaisuudet, kuten äänen normalisoinnin, taajuuskorjaimet ja visualisoijat, mutta joissakin järjestelmissä tämä voi aiheuttaa ääniraitojen pätkimistä.",
|
||||
"settings.warn.audio.enableAdvancedFunctionality.lowcores": "Cider uskoo, että tietokoneesi ei voi käsitellä näitä ominaisuuksia. Oletko varma, että haluat jatkaa?",
|
||||
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Äänen normalisointi",
|
||||
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normalisoi yksittäisten kappaleiden huippuäänenvoimakkuuden luodakseen yhtenäisemmän kuuntelukokemuksen. (Ei toimi käyttäjien lataamilla kappaleilla)",
|
||||
"settings.header.visual": "Ulkonäkö",
|
||||
"settings.header.visual.description": "Muuta ulkonäköä.",
|
||||
"settings.option.visual.windowBackgroundStyle": "Taustatyyli",
|
||||
"settings.header.visual.windowBackgroundStyle.none": "Ei taustatyyliä",
|
||||
"settings.header.visual.windowBackgroundStyle.artwork": "Taideteos",
|
||||
"settings.header.visual.windowBackgroundStyle.image": "Kuva",
|
||||
"settings.option.visual.animatedArtwork": "Animoitu taideteos",
|
||||
"settings.header.visual.animatedArtwork.always": "Aina",
|
||||
"settings.header.visual.animatedArtwork.limited": "Ainoastaa sivuilla joilla se on tarvittua",
|
||||
"settings.header.visual.animatedArtwork.disable": "Ei koskaan",
|
||||
"settings.option.visual.animatedArtworkQuality": "Animoinnin laatu",
|
||||
"settings.header.visual.animatedArtworkQuality.low": "Alhainen",
|
||||
"settings.header.visual.animatedArtworkQuality.medium": "Keskinkertainen",
|
||||
"settings.header.visual.animatedArtworkQuality.high": "Korkea",
|
||||
"settings.header.visual.animatedArtworkQuality.veryHigh": "Erittäin korkea",
|
||||
"settings.header.visual.animatedArtworkQuality.extreme": "Korkein",
|
||||
"settings.option.visual.animatedWindowBackground": "Animoitu tausta",
|
||||
"settings.option.visual.hardwareAcceleration": "Laitteistokiihdytys",
|
||||
"settings.option.visual.hardwareAcceleration.description": "Vaatii uudelleenkäynnistyksen",
|
||||
"settings.header.visual.hardwareAcceleration.default": "Vakio",
|
||||
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
|
||||
"settings.option.visual.showPersonalInfo": "Näytä henkilökohtaiset tiedot",
|
||||
"settings.header.lyrics": "Sanoitukset",
|
||||
"settings.header.lyrics.description": "Muuta sanoitusasetuksia",
|
||||
"settings.option.lyrics.enableMusixmatch": "Käytä MusicXMatchia Apple Music sanoituksien sijaan",
|
||||
"settings.option.lyrics.enableMusixmatchKaraoke": "Aktivoi karaoketila (Vain MusicXMatch)",
|
||||
"settings.option.lyrics.musixmatchPreferredLanguage": "MusicXMatch ensisijainen kieli",
|
||||
"settings.option.lyrics.enableYoutubeLyrics": "Käytä Youtube sanoituksia videoissa",
|
||||
"settings.header.connectivity": "Yhteys",
|
||||
"settings.header.connectivity.description": "Muuta yhteysasetuksia",
|
||||
"settings.option.connectivity.discordRPC": "Discord integraatio (discordRPC)",
|
||||
"settings.option.connectivity.playbackNotifications": "Toistoilmoitukset",
|
||||
"settings.option.connectivity.discordRPC.clearOnPause": "Poista Discord ilmoitus, kun tauotat kappaleen",
|
||||
"settings.option.connectivity.lastfmScrobble": "Last.fm integraatio",
|
||||
"settings.option.connectivity.lastfmScrobble.delay": "Last.fm viive i %",
|
||||
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Näytä mikä kappale Last.fm palvelussa",
|
||||
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Älä näytä extra tietoja Last.fm palvelussa",
|
||||
"settings.option.connectivity.lastfmScrobble.filterLoop": "Suodata pakkotoisteut kappaleet",
|
||||
"settings.header.experimental": "Testi",
|
||||
"settings.header.experimental.description": "Muuta testi asetuksia.",
|
||||
"settings.option.experimental.compactUI": "Kompakti näkymä",
|
||||
"settings.option.window.close_button_hide": "Suljenappi pienentää Cider ikkunan",
|
||||
"spatial.notTurnedOn": "Äänen spatialisointi on poistettu käytöstä. Ota se käyttöön ennen käyttöä.",
|
||||
"spatial.spatialProperties": "Äänen spatialisointi asetukset",
|
||||
"spatial.width": "Leveys",
|
||||
"spatial.height": "Korkeus",
|
||||
"spatial.depth": "Syvyys",
|
||||
"spatial.gain": "Tilavyöhyke",
|
||||
"spatial.roomMaterials": "Huoneen materiaalit",
|
||||
"spatial.roomDimensions": "Huoneen koko",
|
||||
"spatial.roomPositions": "Huoneen sijainti",
|
||||
"spatial.setDimensions": "Valitse koko",
|
||||
"spatial.setPositions": "Valitse sijainnit",
|
||||
"spatial.up": "Ylös",
|
||||
"spatial.front": "Eteenpäin",
|
||||
"spatial.left": "Vasemmalle",
|
||||
"spatial.right": "Oikealle",
|
||||
"spatial.back": "Taaksepäin",
|
||||
"spatial.down": "Alaspäin",
|
||||
"spatial.listener": "Kuuntelija",
|
||||
"spatial.audioSource": "Äänenlähde",
|
||||
"settings.header.unfinished": "Keskeneräinen",
|
||||
"remote.web.title": "Ciderin kaukosäädin",
|
||||
"remote.web.description": "Skannaa QR-koodi yhdistääksesi puhelimesi tämän Cider-instanssin kanssa",
|
||||
"about.thanks": "Suuri kiitos Cider Collective Teamille ja kaikille avustajillemme.",
|
||||
"oobe.yes": "Kyllä",
|
||||
"oobe.no": "Ei",
|
||||
"oobe.next": "Seuraava",
|
||||
"oobe.previous": "Edellinen",
|
||||
"oobe.done": "Valmis",
|
||||
"oobe.amupsell.title": "Ennenkuin aloitamme",
|
||||
"oobe.amupsell.text": "Cider vaatii aktiivisen, maksullisen Apple Music -tilauksen\nCider ei toimi Apple Music Voice Planin tai joidenkin tarjouskilpailutilausten kanssa. Jos sinulla on jo hyväksytty Apple Music -tilaus, napsauta Seuraava jatkaaksesi",
|
||||
"oobe.amupsell.subscribeBtn": "Tilaa Apple Music",
|
||||
"oobe.amupsell.explainBtn": "Selitä",
|
||||
"oobe.amupsell.subscribeUrl": "https://apple.co/3MdqJVQ",
|
||||
"oobe.amupsell.amWebUrl": "https://beta.music.apple.com/",
|
||||
"oobe.amupsell.promoExplained": "Joillakin promootiotilauksilla ja muilla kuin yhdysvaltalaisilla Apple Music -kokeilutilauksilla ei ole pääsyä vaadittuihin Apple Music Web Player API:ihin, joita Cider tarvitsee toimiakseen. Tarkistaaksesi, toimiiko aktiivinen kokeiluversiosi Ciderin kanssa, mene osoitteeseen: <a href='{{ subscribeUrl }}'>{{ subscribeUrl }}</a>",
|
||||
"oobe.intro.title": "Tervetuloa Cideriin",
|
||||
"oobe.intro.subtitle": "",
|
||||
"oobe.intro.text": "Otetaan käyttöön muutamia asioita, jotta voit käyttää Cideriä haluamallasi tavalla. Voit aina muuttaa näitä asetuksia myöhemmin.",
|
||||
"oobe.general.title": "Yleistä",
|
||||
"oobe.general.subtitle": "",
|
||||
"oobe.general.text": "",
|
||||
"oobe.audio.title": "Ääni",
|
||||
"oobe.audio.subtitle": "",
|
||||
"oobe.audio.text": "Cider sisältää mukautetun viritetyn ja suunnitellun äänipinon, joka tarjoaa rikkaan korkealaatuisen äänikokemuksen.\nSisältää Cider Adrenaliinin, Atmosphere Realizerin ja Spatialized Audion.\nTämän toiminnon mahdollistamiseksi \"Advanced Audio Functionality\" on otettava käyttöön.\nOta käyttöön Advanced Audio Toiminnallisuus antaa sinulle pääsyn näihin parannuksiin Cider Audio Labsissa, joka löytyy sovelluksen asetuksista.",
|
||||
"oobe.audio.advancedFunctionality": "",
|
||||
"oobe.visual.title": "Ulkonäkö",
|
||||
"oobe.visual.subtitle": "",
|
||||
"oobe.visual.text": "",
|
||||
"oobe.visual.layout.text": "Ciderissä on kaksi erilaista ikkuna-asettelua.\nMaverick on iTunesin kaltainen asettelu, jossa soitin on ikkunan yläosassa.\nMojave on Cider Collectiven luoma uusi kierros.\n\nVoit muuttaa asettelua milloin tahansa asetuksista.",
|
||||
"oobe.visual.suggestingThemes": "Teema on loistava tapa muokata kokemustasi. Tässä on muutamia ehdotuksia: ",
|
||||
"oobe.visual.suggestingThemes.subtext": "(Nämä teemat ladataan GitHubista)",
|
||||
"oobe.visual.suggestingThemes.default": "Cider",
|
||||
"oobe.visual.suggestingThemes.default.text": "Klassinen Ciderteema.",
|
||||
"oobe.visual.suggestingThemes.dark": "Pimeys",
|
||||
"oobe.visual.suggestingThemes.dark.text": "Pimeys",
|
||||
"oobe.visual.suggestingThemes.community1": "Groovy",
|
||||
"oobe.visual.suggestingThemes.community1.text": "WinUI-vaikutteinen teema",
|
||||
"oobe.visual.suggestingThemes.community2": "iTheme",
|
||||
"oobe.visual.suggestingThemes.community2.text": "Klassinen Big fruit teema",
|
||||
"oobe.visual.suggestingThemes.community3": "Dracula",
|
||||
"oobe.visual.suggestingThemes.community3.text": "Ikoninen Dracula-värimaailma",
|
||||
"oobe.amsignin.title": "",
|
||||
"share.platform.twitter.tweet": "Kuuntele kappaletta {{song}} Apple Musicissa.\n\n{{url}}\n\n#AppleMusic #Cider",
|
||||
"share.platform.twitter": "Twitter",
|
||||
"share.platform.facebook": "Facebook",
|
||||
"share.platform.reddit": "Reddit",
|
||||
"share.platform.telegram": "Telegram",
|
||||
"share.platform.whatsapp": "WhatsApp",
|
||||
"share.platform.messenger": "Messenger",
|
||||
"share.platform.email": "Sähköposti",
|
||||
"share.platform.songLink": "Kopioi song.link",
|
||||
"share.platform.clipboard": "Kopioi linkki"
|
||||
}
|
||||
|
||||
"i18n.languageName": "Suomi (FI)",
|
||||
"i18n.languageNameEnglish": "Finnish (FI)",
|
||||
"i18n.category": "main",
|
||||
"i18n.authors": "@marcusziade",
|
||||
"app.name": "Cider",
|
||||
"date.format": "${d} ${m}, ${y}",
|
||||
"dialog.cancel": "Peruuta",
|
||||
"dialog.ok": "OK",
|
||||
"notification.updatingLibrarySongs": "Päivitä kirjaston kappaleet...",
|
||||
"notification.updatingLibraryAlbums": "Päivitä kirjaston albumit...",
|
||||
"notification.updatingLibraryArtists": "Päivitä kirjaston artistit...",
|
||||
"term.appleInc": "Apple Inc.",
|
||||
"term.appleMusic": "Apple Music",
|
||||
"term.applePodcasts": "Apple Podcasts",
|
||||
"term.itunes": "iTunes",
|
||||
"term.github": "GitHub",
|
||||
"term.discord": "Discord",
|
||||
"term.learnMore": "Näytä lisää",
|
||||
"term.accountSettings": "Tilin asetukset",
|
||||
"term.logout": "Kirjaudu ulos",
|
||||
"term.login": "Kirjaudu sisään",
|
||||
"term.about": "Lisätietoja",
|
||||
"term.privateSession": "Yksityinen tila",
|
||||
"term.queue": "Jono",
|
||||
"term.search": "Etsi",
|
||||
"term.library": "Kirjasto",
|
||||
"term.listenNow": "Kuuntele nyt",
|
||||
"term.browse": "Selaa",
|
||||
"term.radio": "Radio",
|
||||
"term.recentlyAdded": "Viimeksi lisätyt",
|
||||
"term.songs": "Kappaleet",
|
||||
"term.albums": "Albumit",
|
||||
"term.artists": "Artistit",
|
||||
"term.podcasts": "Podcastit",
|
||||
"term.playlists": "Soittolistat",
|
||||
"term.playlist": "Soittolista",
|
||||
"term.newPlaylist": "Uusi soittolista",
|
||||
"term.newPlaylistFolder": "Uusi soittolistakansio",
|
||||
"term.createNewPlaylist": "Luo uusi soittolista",
|
||||
"term.createNewPlaylistFolder": "Luo uusi soittolistakansio",
|
||||
"term.deletePlaylist": "Oletko varma, että haluat poistaa tämän soittolistan?",
|
||||
"term.play": "Soita",
|
||||
"term.pause": "Tauko",
|
||||
"term.previous": "Edellinen",
|
||||
"term.next": "Seuraava",
|
||||
"term.shuffle": "Sekoita",
|
||||
"term.repeat": "Toista",
|
||||
"term.volume": "Volyymi",
|
||||
"term.mute": "Mykistä",
|
||||
"term.unmute": "Poista mykistys",
|
||||
"term.share": "Jaa",
|
||||
"term.settings": "Asetukset",
|
||||
"term.seeAll": "Näe kaikki",
|
||||
"term.sortBy": "Järjestä",
|
||||
"term.sortBy.album": "Järjestä albumin mukaan",
|
||||
"term.sortBy.artist": "Järjestä artistin mukaan",
|
||||
"term.sortBy.name": "Järjestä nimen mukaan",
|
||||
"term.sortBy.genre": "Järjestä genren mukaan",
|
||||
"term.sortBy.releaseDate": "Julkaisupäivä",
|
||||
"term.sortBy.duration": "Pituus",
|
||||
"term.sortOrder": "Järjestys",
|
||||
"term.sortOrder.ascending": "Nousevassa järjestyksessä",
|
||||
"term.sortOrder.descending": "Laskevassa järjestyksessä",
|
||||
"term.viewAs": "Näytä kuin",
|
||||
"term.viewAs.coverArt": "Kansikuva",
|
||||
"term.viewAs.list": "Lista",
|
||||
"term.size": "Koko",
|
||||
"term.size.normal": "Normaali",
|
||||
"term.size.compact": "Kompakti",
|
||||
"term.enable": "Ota käyttöön",
|
||||
"term.disable": "Poista käytöstä",
|
||||
"term.enabled": "Käytössä",
|
||||
"term.disabled": "Poissa käytöstä",
|
||||
"term.connect": "Yhdistä",
|
||||
"term.connecting": "Yhdistää",
|
||||
"term.disconnect": "Katkaise",
|
||||
"term.authed": "Tunnistettu",
|
||||
"term.confirm": "Vahvista",
|
||||
"term.more": "Lisää",
|
||||
"term.less": "Vähemmän",
|
||||
"term.showMore": "Näytä lisää",
|
||||
"term.showLess": "Näytä vähemmän",
|
||||
"term.topSongs": "Huippukappaleet",
|
||||
"term.latestReleases": "Viimeisimmät julkaisut",
|
||||
"term.time.added": "Lisätty",
|
||||
"term.time.released": "Julkaistu",
|
||||
"term.time.updated": "Päivitetty",
|
||||
"term.time.hours": "Tuntia",
|
||||
"term.time.hour": "Tunti",
|
||||
"term.time.minutes": "Minuuttiaa",
|
||||
"term.time.minute": "Minuutti",
|
||||
"term.time.seconds": "Sekuntia",
|
||||
"term.time.second": "Sekunti",
|
||||
"term.fullscreenView": "Koko näytön näkymä",
|
||||
"term.defaultView": "Oletusnäkymä",
|
||||
"term.audioSettings": "Ääniasetukset",
|
||||
"term.clearAll": "Puhdista kaikki",
|
||||
"term.recentStations": "Viimeisimmät asemat",
|
||||
"term.language": "Kieli",
|
||||
"term.funLanguages": "Hauskat kielet",
|
||||
"term.noLyrics": "Ei sanoituksia",
|
||||
"term.copyright": "Tekijänoikeus",
|
||||
"term.rightsReserved": "Oikeudet pidätetään",
|
||||
"term.sponsor": "Sponsoroi tätä projektia",
|
||||
"term.ciderTeam": "Cider tiimi",
|
||||
"term.developer": "Kehittäjä",
|
||||
"term.socialTeam": "Sosiaalinen tiimi",
|
||||
"term.socials": "Sosiaaliset mediat",
|
||||
"term.contributors": "Avustaja",
|
||||
"term.equalizer": "Taajuuskorjain",
|
||||
"term.reset": "Nollaa",
|
||||
"term.tracks": "Kappaleita",
|
||||
"term.videos": "Videoita",
|
||||
"term.menu": "Valikko",
|
||||
"term.check": "Tarkista",
|
||||
"term.aboutArtist": "Lisätiedot {{artistName}}",
|
||||
"home.title": "Koti",
|
||||
"home.recentlyPlayed": "Viimeksi soitetut",
|
||||
"home.recentlyAdded": "Viimeksi lisätyt",
|
||||
"home.artistsFeed": "Artisti syöte",
|
||||
"home.artistsFeed.noArtist": "Seuraa joitain artisteja nähdäksesi heidän uusimmat julkaisunsa.",
|
||||
"home.madeForYou": "Tehty sinulle",
|
||||
"home.friendsListeningTo": "Kaverit kuuntelee",
|
||||
"home.followedArtists": "Seuratut artistit",
|
||||
"error.appleMusicSubRequired": "Apple Music vaatii tilauksen.",
|
||||
"error.connectionError": "Apple Musiciin yhdistämisessä oli ongelma.",
|
||||
"error.noResults": "Ei tuloksia.",
|
||||
"error.noResults.description": "Kokeile uutta hakua.",
|
||||
"podcast.followOnCider": "Seuraa Ciderissa",
|
||||
"podcast.followedOnCider": "Seurattu Ciderissa",
|
||||
"podcast.subscribeOnItunes": "Tilaa iTunesissa",
|
||||
"podcast.subscribedOnItunes": "Tilattu iTunesissa",
|
||||
"podcast.itunesStore": "iTunes Store",
|
||||
"podcast.episodes": "Jakso",
|
||||
"podcast.playEpisode": "Toista jakso",
|
||||
"podcast.website": "Avaa nettisivu",
|
||||
"action.addToLibrary": "Lisää kirjastoon",
|
||||
"action.addToLibrary.success": "Lisätty kirjastoon",
|
||||
"action.addToLibrary.error": "Virhe lisättäessä kirjastoon",
|
||||
"action.removeFromLibrary": "Poista kirjastosta",
|
||||
"action.removeFromLibrary.success": "Poistettu kirjastosta",
|
||||
"action.addToQueue": "Lisää jonoon",
|
||||
"action.addToQueue.success": "Lisätty jonoon",
|
||||
"action.addToQueue.error": "Virhe lisättäessä jonoon",
|
||||
"action.removeFromQueue": "Poista jonosta",
|
||||
"action.removeFromQueue.success": "Poistettu jonosta",
|
||||
"action.removeFromQueue.error": "Virhe poistettaessa jonosta",
|
||||
"action.createPlaylist": "Luo uusi soittolista",
|
||||
"action.addToPlaylist": "Lisää soittolistaan",
|
||||
"action.removeFromPlaylist": "Poista soittolistasta",
|
||||
"action.addToFavorites": "Lisää suosikkeihin",
|
||||
"action.follow": "Seuraa",
|
||||
"action.follow.success": "Seurattu",
|
||||
"action.follow.error": "Virhe seurattaessa",
|
||||
"action.unfollow": "Lopeta seuraaminen",
|
||||
"action.unfollow.success": "Seuraaminen lopetettu",
|
||||
"action.unfollow.error": "Virhe seuraamisen lopetuksessa",
|
||||
"action.playNext": "Toista seuraavaksi",
|
||||
"action.playLater": "Toista myöhemmin",
|
||||
"action.startRadio": "Aloita radio",
|
||||
"action.goToArtist": "Näytä artisti",
|
||||
"action.goToAlbum": "Näytä albumi",
|
||||
"action.moveToTop": "Siirrä kärkeen",
|
||||
"action.share": "Jaa",
|
||||
"action.rename": "Nimeä uudelleen",
|
||||
"action.love": "Tykkää",
|
||||
"action.unlove": "Poista tykkäys",
|
||||
"action.dislike": "En tykkää",
|
||||
"action.undoDislike": "Kumoa en tykkää",
|
||||
"action.showWebRemoteQR": "Cider web kaukoohjain",
|
||||
"action.playTracksNext": "Toista ${app.selectedMediaItems.length} kappaleet seuraavaksi",
|
||||
"action.playTracksLater": "Toista ${app.selectedMediaItems.length} kappaleet myöhemmin",
|
||||
"action.removeTracks": "Poista ${self.selectedItems.length} kappaleet jonosta",
|
||||
"action.import": "Tuonti",
|
||||
"action.export": "Vienti",
|
||||
"action.showAlbum": "Näytä albumi",
|
||||
"action.tray.minimize": "Pienennä",
|
||||
"action.tray.quit": "Sammuta",
|
||||
"action.tray.show": "Näytä",
|
||||
"action.update": "Päivitä",
|
||||
"settings.header.general": "Yleistä",
|
||||
"settings.header.general.description": "Muuta yleisasetuksia",
|
||||
"settings.option.general.language": "Kieli",
|
||||
"settings.option.general.language.main": "Kieli",
|
||||
"settings.option.general.language.fun": "Hauskat kielet",
|
||||
"settings.option.general.language.unsorted": "Lajittelematon",
|
||||
"settings.header.audio": "Ääni",
|
||||
"settings.header.audio.description": "Muuta ääniasetuksia",
|
||||
"settings.option.audio.quality": "Äänenlaatu",
|
||||
"settings.header.audio.quality.high": "Korkea",
|
||||
"settings.option.audio.seamlessTransition": "Saumaton siirtyminen",
|
||||
"settings.option.audio.enableAdvancedFunctionality": "Ota lisätoiminnot käyttöön",
|
||||
"settings.option.audio.enableAdvancedFunctionality.description": "AudioContext-toiminnon ottaminen käyttöön mahdollistaa laajennetut ääniominaisuudet, kuten äänen normalisoinnin, taajuuskorjaimet ja visualisoijat, mutta joissakin järjestelmissä tämä voi aiheuttaa ääniraitojen pätkimistä.",
|
||||
"settings.warn.audio.enableAdvancedFunctionality.lowcores": "Cider uskoo, että tietokoneesi ei voi käsitellä näitä ominaisuuksia. Oletko varma, että haluat jatkaa?",
|
||||
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Äänen normalisointi",
|
||||
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normalisoi yksittäisten kappaleiden huippuäänenvoimakkuuden luodakseen yhtenäisemmän kuuntelukokemuksen. (Ei toimi käyttäjien lataamilla kappaleilla)",
|
||||
"settings.header.visual": "Ulkonäkö",
|
||||
"settings.header.visual.description": "Muuta ulkonäköä.",
|
||||
"settings.option.visual.windowBackgroundStyle": "Taustatyyli",
|
||||
"settings.header.visual.windowBackgroundStyle.none": "Ei taustatyyliä",
|
||||
"settings.header.visual.windowBackgroundStyle.artwork": "Taideteos",
|
||||
"settings.header.visual.windowBackgroundStyle.image": "Kuva",
|
||||
"settings.option.visual.animatedArtwork": "Animoitu taideteos",
|
||||
"settings.header.visual.animatedArtwork.always": "Aina",
|
||||
"settings.header.visual.animatedArtwork.limited": "Ainoastaa sivuilla joilla se on tarvittua",
|
||||
"settings.header.visual.animatedArtwork.disable": "Ei koskaan",
|
||||
"settings.option.visual.animatedArtworkQuality": "Animoinnin laatu",
|
||||
"settings.header.visual.animatedArtworkQuality.low": "Alhainen",
|
||||
"settings.header.visual.animatedArtworkQuality.medium": "Keskinkertainen",
|
||||
"settings.header.visual.animatedArtworkQuality.high": "Korkea",
|
||||
"settings.header.visual.animatedArtworkQuality.veryHigh": "Erittäin korkea",
|
||||
"settings.header.visual.animatedArtworkQuality.extreme": "Korkein",
|
||||
"settings.option.visual.animatedWindowBackground": "Animoitu tausta",
|
||||
"settings.option.visual.hardwareAcceleration": "Laitteistokiihdytys",
|
||||
"settings.option.visual.hardwareAcceleration.description": "Vaatii uudelleenkäynnistyksen",
|
||||
"settings.header.visual.hardwareAcceleration.default": "Vakio",
|
||||
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
|
||||
"settings.option.visual.showPersonalInfo": "Näytä henkilökohtaiset tiedot",
|
||||
"settings.header.lyrics": "Sanoitukset",
|
||||
"settings.header.lyrics.description": "Muuta sanoitusasetuksia",
|
||||
"settings.option.lyrics.enableMusixmatch": "Käytä MusicXMatchia Apple Music sanoituksien sijaan",
|
||||
"settings.option.lyrics.enableMusixmatchKaraoke": "Aktivoi karaoketila (Vain MusicXMatch)",
|
||||
"settings.option.lyrics.musixmatchPreferredLanguage": "MusicXMatch ensisijainen kieli",
|
||||
"settings.option.lyrics.enableYoutubeLyrics": "Käytä Youtube sanoituksia videoissa",
|
||||
"settings.header.connectivity": "Yhteys",
|
||||
"settings.header.connectivity.description": "Muuta yhteysasetuksia",
|
||||
"settings.option.connectivity.discordRPC": "Discord integraatio (discordRPC)",
|
||||
"settings.option.connectivity.playbackNotifications": "Toistoilmoitukset",
|
||||
"settings.option.connectivity.discordRPC.clearOnPause": "Poista Discord ilmoitus, kun tauotat kappaleen",
|
||||
"settings.option.connectivity.lastfmScrobble": "Last.fm integraatio",
|
||||
"settings.option.connectivity.lastfmScrobble.delay": "Last.fm viive i %",
|
||||
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Näytä mikä kappale Last.fm palvelussa",
|
||||
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Älä näytä extra tietoja Last.fm palvelussa",
|
||||
"settings.option.connectivity.lastfmScrobble.filterLoop": "Suodata pakkotoisteut kappaleet",
|
||||
"settings.header.experimental": "Testi",
|
||||
"settings.header.experimental.description": "Muuta testi asetuksia.",
|
||||
"settings.option.experimental.compactUI": "Kompakti näkymä",
|
||||
"settings.option.window.close_button_hide": "Suljenappi pienentää Cider ikkunan",
|
||||
"spatial.notTurnedOn": "Äänen spatialisointi on poistettu käytöstä. Ota se käyttöön ennen käyttöä.",
|
||||
"spatial.spatialProperties": "Äänen spatialisointi asetukset",
|
||||
"spatial.width": "Leveys",
|
||||
"spatial.height": "Korkeus",
|
||||
"spatial.depth": "Syvyys",
|
||||
"spatial.gain": "Tilavyöhyke",
|
||||
"spatial.roomMaterials": "Huoneen materiaalit",
|
||||
"spatial.roomDimensions": "Huoneen koko",
|
||||
"spatial.roomPositions": "Huoneen sijainti",
|
||||
"spatial.setDimensions": "Valitse koko",
|
||||
"spatial.setPositions": "Valitse sijainnit",
|
||||
"spatial.up": "Ylös",
|
||||
"spatial.front": "Eteenpäin",
|
||||
"spatial.left": "Vasemmalle",
|
||||
"spatial.right": "Oikealle",
|
||||
"spatial.back": "Taaksepäin",
|
||||
"spatial.down": "Alaspäin",
|
||||
"spatial.listener": "Kuuntelija",
|
||||
"spatial.audioSource": "Äänenlähde",
|
||||
"settings.header.unfinished": "Keskeneräinen",
|
||||
"remote.web.title": "Ciderin kaukosäädin",
|
||||
"remote.web.description": "Skannaa QR-koodi yhdistääksesi puhelimesi tämän Cider-instanssin kanssa",
|
||||
"about.thanks": "Suuri kiitos Cider Collective Teamille ja kaikille avustajillemme.",
|
||||
"oobe.yes": "Kyllä",
|
||||
"oobe.no": "Ei",
|
||||
"oobe.next": "Seuraava",
|
||||
"oobe.previous": "Edellinen",
|
||||
"oobe.done": "Valmis",
|
||||
"oobe.amupsell.title": "Ennenkuin aloitamme",
|
||||
"oobe.amupsell.text": "Cider vaatii aktiivisen, maksullisen Apple Music -tilauksen\nCider ei toimi Apple Music Voice Planin tai joidenkin tarjouskilpailutilausten kanssa. Jos sinulla on jo hyväksytty Apple Music -tilaus, napsauta Seuraava jatkaaksesi",
|
||||
"oobe.amupsell.subscribeBtn": "Tilaa Apple Music",
|
||||
"oobe.amupsell.explainBtn": "Selitä",
|
||||
"oobe.amupsell.subscribeUrl": "https://apple.co/3MdqJVQ",
|
||||
"oobe.amupsell.amWebUrl": "https://beta.music.apple.com/",
|
||||
"oobe.amupsell.promoExplained": "Joillakin promootiotilauksilla ja muilla kuin yhdysvaltalaisilla Apple Music -kokeilutilauksilla ei ole pääsyä vaadittuihin Apple Music Web Player API:ihin, joita Cider tarvitsee toimiakseen. Tarkistaaksesi, toimiiko aktiivinen kokeiluversiosi Ciderin kanssa, mene osoitteeseen: <a href='{{ subscribeUrl }}'>{{ subscribeUrl }}</a>",
|
||||
"oobe.intro.title": "Tervetuloa Cideriin",
|
||||
"oobe.intro.subtitle": "",
|
||||
"oobe.intro.text": "Otetaan käyttöön muutamia asioita, jotta voit käyttää Cideriä haluamallasi tavalla. Voit aina muuttaa näitä asetuksia myöhemmin.",
|
||||
"oobe.general.title": "Yleistä",
|
||||
"oobe.general.subtitle": "",
|
||||
"oobe.general.text": "",
|
||||
"oobe.audio.title": "Ääni",
|
||||
"oobe.audio.subtitle": "",
|
||||
"oobe.audio.text": "Cider sisältää mukautetun viritetyn ja suunnitellun äänipinon, joka tarjoaa rikkaan korkealaatuisen äänikokemuksen.\nSisältää Cider Adrenaliinin, Atmosphere Realizerin ja Spatialized Audion.\nTämän toiminnon mahdollistamiseksi \"Advanced Audio Functionality\" on otettava käyttöön.\nOta käyttöön Advanced Audio Toiminnallisuus antaa sinulle pääsyn näihin parannuksiin Cider Audio Labsissa, joka löytyy sovelluksen asetuksista.",
|
||||
"oobe.audio.advancedFunctionality": "",
|
||||
"oobe.visual.title": "Ulkonäkö",
|
||||
"oobe.visual.subtitle": "",
|
||||
"oobe.visual.text": "",
|
||||
"oobe.visual.layout.text": "Ciderissä on kaksi erilaista ikkuna-asettelua.\nMaverick on iTunesin kaltainen asettelu, jossa soitin on ikkunan yläosassa.\nMojave on Cider Collectiven luoma uusi kierros.\n\nVoit muuttaa asettelua milloin tahansa asetuksista.",
|
||||
"oobe.visual.suggestingThemes": "Teema on loistava tapa muokata kokemustasi. Tässä on muutamia ehdotuksia: ",
|
||||
"oobe.visual.suggestingThemes.subtext": "(Nämä teemat ladataan GitHubista)",
|
||||
"oobe.visual.suggestingThemes.default": "Cider",
|
||||
"oobe.visual.suggestingThemes.default.text": "Klassinen Ciderteema.",
|
||||
"oobe.visual.suggestingThemes.dark": "Pimeys",
|
||||
"oobe.visual.suggestingThemes.dark.text": "Pimeys",
|
||||
"oobe.visual.suggestingThemes.community1": "Groovy",
|
||||
"oobe.visual.suggestingThemes.community1.text": "WinUI-vaikutteinen teema",
|
||||
"oobe.visual.suggestingThemes.community2": "iTheme",
|
||||
"oobe.visual.suggestingThemes.community2.text": "Klassinen Big fruit teema",
|
||||
"oobe.visual.suggestingThemes.community3": "Dracula",
|
||||
"oobe.visual.suggestingThemes.community3.text": "Ikoninen Dracula-värimaailma",
|
||||
"oobe.amsignin.title": "",
|
||||
"share.platform.twitter.tweet": "Kuuntele kappaletta {{song}} Apple Musicissa.\n\n{{url}}\n\n#AppleMusic #Cider",
|
||||
"share.platform.twitter": "Twitter",
|
||||
"share.platform.facebook": "Facebook",
|
||||
"share.platform.reddit": "Reddit",
|
||||
"share.platform.telegram": "Telegram",
|
||||
"share.platform.whatsapp": "WhatsApp",
|
||||
"share.platform.messenger": "Messenger",
|
||||
"share.platform.email": "Sähköposti",
|
||||
"share.platform.songLink": "Kopioi song.link",
|
||||
"share.platform.clipboard": "Kopioi linkki"
|
||||
}
|
||||
|
|
|
@ -195,16 +195,16 @@
|
|||
"term.confirmLogout": "Apakah Anda yakin ingin keluar??",
|
||||
"term.creditDesignedBy": "Dirancang oleh ${authorUsername}",
|
||||
"term.discNumber": "Kaset ${discNumber}",
|
||||
"term.reload" : "Muat ulang Cider?",
|
||||
"term.toggleprivate" : "Nyalakan Sesi Pribadi",
|
||||
"term.webremote" : "Remot Web",
|
||||
"term.cast" : "Transmisi",
|
||||
"term.cast2" : "Transmisikan ke Perangkat",
|
||||
"term.quit" : "Keluar",
|
||||
"term.zoomin" : "Perbesar",
|
||||
"term.zoomout" : "Perkecil",
|
||||
"term.zoomreset" : "Atur Ulang",
|
||||
"term.fullscreen" : "Layar Penuh",
|
||||
"term.reload": "Muat ulang Cider?",
|
||||
"term.toggleprivate": "Nyalakan Sesi Pribadi",
|
||||
"term.webremote": "Remot Web",
|
||||
"term.cast": "Transmisi",
|
||||
"term.cast2": "Transmisikan ke Perangkat",
|
||||
"term.quit": "Keluar",
|
||||
"term.zoomin": "Perbesar",
|
||||
"term.zoomout": "Perkecil",
|
||||
"term.zoomreset": "Atur Ulang",
|
||||
"term.fullscreen": "Layar Penuh",
|
||||
"home.syncFavorites": "Sinkronkan Favorit",
|
||||
"home.syncFavorites.gettingArtists": "Mendapatkan artis favorit",
|
||||
"home.title": "Beranda",
|
||||
|
@ -331,10 +331,10 @@
|
|||
"settings.option.general.resumebehavior.locally.description": "Cider akan melanjutkan sesi terakhir Anda di perangkat ini.",
|
||||
"settings.option.general.resumebehavior.history": "Riwayat",
|
||||
"settings.option.general.resumebehavior.history.description": "Cider akan melanjutkan lagu terakhir dari riwayat Apple Music di seluruh perangkat Anda.",
|
||||
"settings.option.general.resumetabs" : "Buka Tab ketika Diluncurkan",
|
||||
"settings.option.general.resumetabs.description" : "Anda dapat memilih tab apa yang akan dibuka ketika Anda membuka Cider.",
|
||||
"settings.option.general.resumetabs.dynamic" : "Dinamis",
|
||||
"settings.option.general.resumetabs.dynamic.description" : "Cider akan membuka tab yang terakhir digunakan",
|
||||
"settings.option.general.resumetabs": "Buka Tab ketika Diluncurkan",
|
||||
"settings.option.general.resumetabs.description": "Anda dapat memilih tab apa yang akan dibuka ketika Anda membuka Cider.",
|
||||
"settings.option.general.resumetabs.dynamic": "Dinamis",
|
||||
"settings.option.general.resumetabs.dynamic.description": "Cider akan membuka tab yang terakhir digunakan",
|
||||
"settings.option.general.language.main": "Bahasa",
|
||||
"settings.option.general.language.fun": "Bahasa Candaan",
|
||||
"settings.option.general.language.unsorted": "Tidak disortir",
|
||||
|
@ -392,7 +392,7 @@
|
|||
"settings.warn.audio.enableAdvancedFunctionality.lowcores": "Cider memperkirakan bahwa PC Anda tidak dapat menggunakan fitur ini. Apakah Anda yakin ingin melanjutkan?",
|
||||
"settings.option.audio.audioLab": "Lab Audio Cider",
|
||||
"settings.option.audio.audioLab.description": "Macam-macam efek audio yang dikembangkan sendiri untuk Cider.",
|
||||
"settings.option.audio.audioLab.subheader": "Dibuat oleh Cider Acoustic Technologies di California",
|
||||
"settings.option.audio.audioLab.subheader": "Dibuat oleh Cider Acoustic Technologies di California",
|
||||
"settings.warn.audioLab.withoutAF": "AudioContext (Fungsi Lanjutan) perlu diaktifkan untuk menggunakan Lab Audio Cider.",
|
||||
"settings.warn.enableAdvancedFunctionality": "AudioContext (Advanced Functionality) dibutuhkan untuk menyalakan fitur ini.",
|
||||
"settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Analog Warmth",
|
||||
|
|
1268
src/i18n/ja_JP.json
1268
src/i18n/ja_JP.json
File diff suppressed because it is too large
Load diff
|
@ -10,7 +10,7 @@
|
|||
"notification.updatingLibrarySongs": "Обновление медиатеки песен...",
|
||||
"notification.updatingLibraryAlbums": "Обновление медиатеки альбомов...",
|
||||
"notification.updatingLibraryArtists": "Обновление медиатеки артистов...",
|
||||
"term.variables": "Переменные",
|
||||
"term.variables": "Переменные",
|
||||
"term.appleInc": "Apple Inc.",
|
||||
"term.appleMusic": "Apple Music",
|
||||
"term.applePodcasts": "Подкасты Apple",
|
||||
|
@ -26,7 +26,7 @@
|
|||
"term.privateSession": "Частная сессия",
|
||||
"term.disablePrivateSession": "Выключить частную сессию",
|
||||
"term.queue": "Очередь",
|
||||
"term.autoplay": "Автовоспроизведение",
|
||||
"term.autoplay": "Автовоспроизведение",
|
||||
"term.lyrics": "Текст",
|
||||
"term.miniplayer": "Мини-проигрыватель",
|
||||
"term.history": "История",
|
||||
|
@ -209,16 +209,16 @@
|
|||
"term.confirmLogout": "Вы уверены, что хотите выйти?",
|
||||
"term.creditDesignedBy": "Разработано ${authorUsername}",
|
||||
"term.discNumber": "Диск ${discNumber}",
|
||||
"term.reload" : "Перезагрузить Cider?",
|
||||
"term.toggleprivate" : "Переключить частную сессию",
|
||||
"term.webremote" : "Web Remote",
|
||||
"term.cast" : "Транслировать",
|
||||
"term.cast2" : "Трансляция на устройства",
|
||||
"term.quit" : "Выход",
|
||||
"term.zoomin" : "Приблизить",
|
||||
"term.zoomout" : "Отдалить",
|
||||
"term.zoomreset" : "Сбросить масштаб",
|
||||
"term.fullscreen" : "Полный экран",
|
||||
"term.reload": "Перезагрузить Cider?",
|
||||
"term.toggleprivate": "Переключить частную сессию",
|
||||
"term.webremote": "Web Remote",
|
||||
"term.cast": "Транслировать",
|
||||
"term.cast2": "Трансляция на устройства",
|
||||
"term.quit": "Выход",
|
||||
"term.zoomin": "Приблизить",
|
||||
"term.zoomout": "Отдалить",
|
||||
"term.zoomreset": "Сбросить масштаб",
|
||||
"term.fullscreen": "Полный экран",
|
||||
"term.nowPlaying": "Сейчас играет",
|
||||
"home.syncFavorites": "Синхронизировать",
|
||||
"home.syncFavorites.gettingArtists": "Получение отслеживаемых исполнителей...",
|
||||
|
@ -347,10 +347,10 @@
|
|||
"settings.option.general.resumebehavior.locally.description": "Cider возобновит ваш последний сеанс на этом компьютере.",
|
||||
"settings.option.general.resumebehavior.history": "История",
|
||||
"settings.option.general.resumebehavior.history.description": "Cider поставит в очередь последнюю песню из вашей общей истории Apple Music на разных устройствах.",
|
||||
"settings.option.general.resumetabs" : "Раздел при запуске",
|
||||
"settings.option.general.resumetabs.description" : "Вы можете выбрать, какой раздел будет открыться при запуске Cider.",
|
||||
"settings.option.general.resumetabs.dynamic" : "Динамически",
|
||||
"settings.option.general.resumetabs.dynamic.description" : "Cider откроет последний использованный раздел.",
|
||||
"settings.option.general.resumetabs": "Раздел при запуске",
|
||||
"settings.option.general.resumetabs.description": "Вы можете выбрать, какой раздел будет открыться при запуске Cider.",
|
||||
"settings.option.general.resumetabs.dynamic": "Динамически",
|
||||
"settings.option.general.resumetabs.dynamic.description": "Cider откроет последний использованный раздел.",
|
||||
"settings.option.general.language.main": "Языки",
|
||||
"settings.option.general.language.fun": "Забавные языки",
|
||||
"settings.option.general.language.unsorted": "Неотсортированные",
|
||||
|
@ -408,9 +408,9 @@
|
|||
"settings.warn.audio.enableAdvancedFunctionality.lowcores": "Cider считает, что ваш компьютер не справится с этими функциями. Вы уверены что хотите продолжить?",
|
||||
"settings.option.audio.audioLab": "Cider Audio Lab",
|
||||
"settings.option.audio.audioLab.description": "Ассортимент звуковых обработок собственной разработки для Cider.",
|
||||
"settings.option.audio.audioLab.subheader": "Разработано Cider Acoustic Technologies в Калифорнии",
|
||||
"settings.option.audio.audioLab.subheader": "Разработано Cider Acoustic Technologies в Калифорнии",
|
||||
"settings.warn.audioLab.withoutAF": "AudioContext (Расширенный функционал) требуется для включения Cider Audio Laboratory.",
|
||||
"settings.warn.enableAdvancedFunctionality": "Для включения этой функции требуется AudioContext (расширенный функционал).",
|
||||
"settings.warn.enableAdvancedFunctionality": "Для включения этой функции требуется AudioContext (расширенный функционал).",
|
||||
"settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Аналоговое звучание",
|
||||
"settings.option.audio.enableAdvancedFunctionality.analogWarmth.description": "Имитирует аналоговое звучание по образцу Korg Nutube 6P1",
|
||||
"settings.option.audio.enableAdvancedFunctionality.analogWarmthIntensity": "Интенсивность аналогового звучания",
|
||||
|
@ -431,7 +431,7 @@
|
|||
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.BSCBM": "Brown Sugar Creme Brûlée Milk",
|
||||
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500B": "Clafoutis aux Cerises",
|
||||
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500C": "Uji Matcha Mochi",
|
||||
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.CUDDLE": "Cuddle Warmth",
|
||||
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.CUDDLE": "Cuddle Warmth",
|
||||
"settings.option.audio.enableAdvancedFunctionality.ciderPPE": "Cider Adrenaline Processor™️",
|
||||
"settings.option.audio.enableAdvancedFunctionality.ciderPPE.description": "Улучшает воспринимаемое качество звука AAC 256 кбит/с за счет использования алгоритма реального времени, использующего как психоакустические модели человеческого слуха, так и характеристики кодирования AAC.",
|
||||
"settings.warn.audio.enableAdvancedFunctionality.ciderPPE.compatibility": "CAP не совместим с пространственным звучанием. Пожалуйста, отключите пространственное звучание, чтобы продолжить.",
|
||||
|
@ -440,7 +440,7 @@
|
|||
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.standard": "Стандартный",
|
||||
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.adaptive": "Адаптивный",
|
||||
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.legacy": "Legacy",
|
||||
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.aggressive": "Агрессивный",
|
||||
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.aggressive": "Агрессивный",
|
||||
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Нормализация звука",
|
||||
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Нормализует пиковую громкость для отдельных треков, чтобы создать более однородное впечатление от прослушивания.",
|
||||
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.disabled": "Управляется Audio Lab",
|
||||
|
|
|
@ -202,16 +202,16 @@
|
|||
"term.confirmLogout": "Are you sure you want to logout?",
|
||||
"term.creditDesignedBy": "Designed by ${authorUsername}",
|
||||
"term.discNumber": "Disc ${discNumber}",
|
||||
"term.reload" : "Reload Cider?",
|
||||
"term.toggleprivate" : "Toggle Private Session",
|
||||
"term.webremote" : "Web Remote",
|
||||
"term.cast" : "Cast",
|
||||
"term.cast2" : "Cast to Devices",
|
||||
"term.quit" : "Quit",
|
||||
"term.zoomin" : "Zoom In",
|
||||
"term.zoomout" : "Zoom Out",
|
||||
"term.zoomreset" : "Reset Zoom",
|
||||
"term.fullscreen" : "Fullscreen",
|
||||
"term.reload": "Reload Cider?",
|
||||
"term.toggleprivate": "Toggle Private Session",
|
||||
"term.webremote": "Web Remote",
|
||||
"term.cast": "Cast",
|
||||
"term.cast2": "Cast to Devices",
|
||||
"term.quit": "Quit",
|
||||
"term.zoomin": "Zoom In",
|
||||
"term.zoomout": "Zoom Out",
|
||||
"term.zoomreset": "Reset Zoom",
|
||||
"term.fullscreen": "Fullscreen",
|
||||
"term.nowPlaying": "Now Playing",
|
||||
"home.syncFavorites": "Sync Favorites",
|
||||
"home.syncFavorites.gettingArtists": "Getting Favorited Artists...",
|
||||
|
@ -340,10 +340,10 @@
|
|||
"settings.option.general.resumebehavior.locally.description": "Cider will resume your last session on this machine.",
|
||||
"settings.option.general.resumebehavior.history": "History",
|
||||
"settings.option.general.resumebehavior.history.description": "Cider will queue the last song from your overall Apple Music history, across devices.",
|
||||
"settings.option.general.resumetabs" : "Open Tab on Launch",
|
||||
"settings.option.general.resumetabs.description" : "You can choose what tab you want to open when you launch Cider.",
|
||||
"settings.option.general.resumetabs.dynamic" : "Dynamic",
|
||||
"settings.option.general.resumetabs.dynamic.description" : "Cider will open the tab that you last used.",
|
||||
"settings.option.general.resumetabs": "Open Tab on Launch",
|
||||
"settings.option.general.resumetabs.description": "You can choose what tab you want to open when you launch Cider.",
|
||||
"settings.option.general.resumetabs.dynamic": "Dynamic",
|
||||
"settings.option.general.resumetabs.dynamic.description": "Cider will open the tab that you last used.",
|
||||
"settings.option.general.language.main": "Languages",
|
||||
"settings.option.general.language.fun": "Fun Languages",
|
||||
"settings.option.general.language.unsorted": "Unsorted",
|
||||
|
@ -401,7 +401,7 @@
|
|||
"settings.warn.audio.enableAdvancedFunctionality.lowcores": "Cider thinks your PC can't handle these features. Are you sure you want to continue?",
|
||||
"settings.option.audio.audioLab": "Cider Audio Lab",
|
||||
"settings.option.audio.audioLab.description": "An assortment of in-house developed audio effects for Cider.",
|
||||
"settings.option.audio.audioLab.subheader": "Designed by Cider Acoustic Technologies in California",
|
||||
"settings.option.audio.audioLab.subheader": "Designed by Cider Acoustic Technologies in California",
|
||||
"settings.warn.audioLab.withoutAF": "AudioContext (Advanced Functionality) is required to enable Cider Audio Laboratory.",
|
||||
"settings.warn.enableAdvancedFunctionality": "AudioContext (Advanced Functionality) is required to enable this feature.",
|
||||
"settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Analog Warmth",
|
||||
|
@ -643,4 +643,4 @@
|
|||
"oobe.visual.suggestingThemes.community3": "Dracula",
|
||||
"oobe.visual.suggestingThemes.community3.text": "The iconic Dracula color scheme.",
|
||||
"oobe.amsignin.title": ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,16 +186,16 @@
|
|||
"term.confirmLogout": "你确定要退出登录吗?",
|
||||
"term.creditDesignedBy": "由 ${authorUsername} 设计",
|
||||
"term.discNumber": "碟 ${discNumber}",
|
||||
"term.reload" : "重新载入 Cider?",
|
||||
"term.reload": "重新载入 Cider?",
|
||||
"term.toggleprivate": "切换隐身聆听",
|
||||
"term.webremote": "远程控制",
|
||||
"term.cast": "投射",
|
||||
"term.cast2" : "投射到设备",
|
||||
"term.quit" : "退出应用",
|
||||
"term.zoomin" : "放大",
|
||||
"term.zoomout" : "缩小",
|
||||
"term.zoomreset" : "重置缩放",
|
||||
"term.fullscreen" : "全屏模式",
|
||||
"term.cast2": "投射到设备",
|
||||
"term.quit": "退出应用",
|
||||
"term.zoomin": "放大",
|
||||
"term.zoomout": "缩小",
|
||||
"term.zoomreset": "重置缩放",
|
||||
"term.fullscreen": "全屏模式",
|
||||
"term.nowPlaying": "正在播放",
|
||||
"home.syncFavorites": "同步喜爱艺人",
|
||||
"home.syncFavorites.gettingArtists": "获取喜爱艺人...",
|
||||
|
|
|
@ -19,10 +19,10 @@
|
|||
"term.accountSettings": "帳戶設定",
|
||||
"term.logout": "登出",
|
||||
"term.login": "登入",
|
||||
"term.quit" : "結束",
|
||||
"term.quit": "結束",
|
||||
"term.about": "關於",
|
||||
"term.cast" : "投影",
|
||||
"term.cast2" : "投影到裝置",
|
||||
"term.cast": "投影",
|
||||
"term.cast2": "投影到裝置",
|
||||
"term.privateSession": "私人時間",
|
||||
"term.queue": "待播清單",
|
||||
"term.lyrics": "歌詞",
|
||||
|
@ -146,14 +146,14 @@
|
|||
"term.plugins": "模組",
|
||||
"term.pluginMenu": "模組選單",
|
||||
"term.pluginMenu.none": "沒有交互式模組",
|
||||
"term.fullscreen" : "全螢幕模式",
|
||||
"term.fullscreen": "全螢幕模式",
|
||||
"home.title": "首頁",
|
||||
"home.recentlyPlayed": "最近播放",
|
||||
"home.recentlyAdded": "最近加入",
|
||||
"home.artistsFeed": "藝人追蹤",
|
||||
"home.artistsFeed.noArtist": "追蹤你喜愛的藝人來取得他們的最新發行歌曲。",
|
||||
"home.syncFavorites" : "同步追蹤" ,
|
||||
"home.syncFavorites.gettingArtists" : "取得追蹤的藝人歌手列表... " ,
|
||||
"home.syncFavorites": "同步追蹤",
|
||||
"home.syncFavorites.gettingArtists": "取得追蹤的藝人歌手列表... ",
|
||||
"home.madeForYou": "為您推薦",
|
||||
"home.friendsListeningTo": "朋友正在聆聽",
|
||||
"home.followedArtists": "追蹤的藝人",
|
||||
|
|
|
@ -1,281 +1,281 @@
|
|||
{
|
||||
"i18n.languageName": "廣東話(香港)",
|
||||
"i18n.languageNameEnglish": "Cantonese (Hong Kong)",
|
||||
"i18n.category": "main",
|
||||
"i18n.authors": "@tszngaiyip @strikesnc",
|
||||
"app.name": "Cider",
|
||||
"date.format": "${y}年${m}月${d}日",
|
||||
"dialog.cancel": "取消",
|
||||
"dialog.ok": "確認",
|
||||
"notification.updatingLibrarySongs": "更新緊資料庫嘅歌曲...",
|
||||
"notification.updatingLibraryAlbums": "更新緊資料庫嘅專輯...",
|
||||
"notification.updatingLibraryArtists": "更新緊資料庫嘅藝人...",
|
||||
"term.appleInc": "Apple Inc.",
|
||||
"term.appleMusic": "Apple Music",
|
||||
"term.applePodcasts": "Apple Podcasts",
|
||||
"term.itunes": "iTunes",
|
||||
"term.github": "GitHub",
|
||||
"term.discord": "Discord",
|
||||
"term.learnMore": "想知更多",
|
||||
"term.accountSettings": "帳號設定",
|
||||
"term.logout": "登出",
|
||||
"term.login": "登入",
|
||||
"term.about": "關於",
|
||||
"term.privateSession": "無痕模式",
|
||||
"term.queue": "待播清單",
|
||||
"term.history": "播放歷史",
|
||||
"term.search": "搵野",
|
||||
"term.library": "資料庫",
|
||||
"term.listenNow": "即刻聽",
|
||||
"term.browse": "瀏覽",
|
||||
"term.radio": "電台",
|
||||
"term.recentlyAdded": "最近加入",
|
||||
"term.songs": "歌曲",
|
||||
"term.albums": "專輯",
|
||||
"term.artists": "藝人",
|
||||
"term.podcasts": "Podcasts",
|
||||
"term.playlists": "播放清單",
|
||||
"term.playlist": "播放清單",
|
||||
"term.newPlaylist": "新播放清單",
|
||||
"term.newPlaylistFolder": "新資料夾",
|
||||
"term.createNewPlaylist": "新增播放清單",
|
||||
"term.createNewPlaylistFolder": "新增資料夾",
|
||||
"term.deletePlaylist": "你係咪要刪除呢個播放清單?",
|
||||
"term.play": "播放",
|
||||
"term.pause": "暫停",
|
||||
"term.previous": "前一首",
|
||||
"term.next": "下一首",
|
||||
"term.shuffle": "隨機播放",
|
||||
"term.repeat": "重複播放",
|
||||
"term.volume": "音量",
|
||||
"term.mute": "靜音",
|
||||
"term.unmute": "取消靜音",
|
||||
"term.share": "分享",
|
||||
"term.share.success": "複製咗喺剪貼簿",
|
||||
"term.settings": "設定",
|
||||
"term.seeAll": "睇哂全部",
|
||||
"term.sortBy": "排序",
|
||||
"term.sortBy.album": "專輯",
|
||||
"term.sortBy.artist": "藝人",
|
||||
"term.sortBy.name": "歌名",
|
||||
"term.sortBy.genre": "音樂風格",
|
||||
"term.sortBy.releaseDate": "幾時出",
|
||||
"term.sortBy.duration": "幾長",
|
||||
"term.sortOrder": "點排",
|
||||
"term.sortOrder.ascending": "順序",
|
||||
"term.sortOrder.descending": "倒序",
|
||||
"term.viewAs": "想點樣顯示",
|
||||
"term.viewAs.coverArt": "專輯封面",
|
||||
"term.viewAs.list": "列表",
|
||||
"term.size": "大細",
|
||||
"term.size.normal": "正常",
|
||||
"term.size.compact": "迫啲",
|
||||
"term.enable": "開",
|
||||
"term.disable": "熄",
|
||||
"term.enabled": "開左",
|
||||
"term.disabled": "熄左",
|
||||
"term.connect": "連結",
|
||||
"term.connecting": "連緊",
|
||||
"term.disconnect": "取消連結",
|
||||
"term.authed": "授權咗",
|
||||
"term.confirm": "確認?",
|
||||
"term.more": "多啲",
|
||||
"term.less": "少啲",
|
||||
"term.showMore": "顯示多啲",
|
||||
"term.showLess": "顯示少啲",
|
||||
"term.topSongs": "熱門歌曲",
|
||||
"term.latestReleases": "最新出嘅",
|
||||
"term.time.added": "加入於",
|
||||
"term.time.released": "發行於",
|
||||
"term.time.updated": "更新於",
|
||||
"term.time.hours": "粒鐘",
|
||||
"term.time.hour": "粒鐘",
|
||||
"term.time.minutes": "分鐘",
|
||||
"term.time.minute": "分鐘",
|
||||
"term.time.seconds": "秒",
|
||||
"term.time.second": "秒",
|
||||
"term.fullscreenView": "用全螢幕睇",
|
||||
"term.defaultView": "平時咁睇",
|
||||
"term.audioSettings": "音訊設定",
|
||||
"term.clearAll": "清除",
|
||||
"term.recentStations": "呢排聽緊嘅",
|
||||
"term.language": "語言",
|
||||
"term.funLanguages": "惡搞",
|
||||
"term.noLyrics": "搵緊... / 搵唔到歌詞。 / 純音樂黎。",
|
||||
"term.copyright": "版權",
|
||||
"term.rightsReserved": "保留一切權利",
|
||||
"term.sponsor": "課金俾呢個Project",
|
||||
"term.ciderTeam": "Cider 團隊",
|
||||
"term.developer": "開發者",
|
||||
"term.socialTeam": "PR",
|
||||
"term.socials": "我哋嘅社群",
|
||||
"term.contributors": "合作人",
|
||||
"term.equalizer": "均衡器 (EQ)",
|
||||
"term.reset": "重設",
|
||||
"term.tracks": "首歌",
|
||||
"term.videos": "影片",
|
||||
"term.menu": "選項",
|
||||
"term.check": "檢查",
|
||||
"term.aboutArtist": "關於 {{artistName}}",
|
||||
"term.topResult": "熱門搜尋結果",
|
||||
"term.sharedPlaylists": "播放清單",
|
||||
"term.people": "個人檔案",
|
||||
"term.newpreset.name": "新EQ範本嘅名",
|
||||
"term.addedpreset": "新增咗",
|
||||
"term.deletepreset.warn": "你係咪要刪除呢個範本?",
|
||||
"term.deletedpreset": "刪除咗",
|
||||
"term.musicVideos": "MV",
|
||||
"term.stations": "電台",
|
||||
"term.radioShows": "電台單集",
|
||||
"term.recordLabels": "唱片公司",
|
||||
"term.videoExtras": "相關嘅片",
|
||||
"home.title": "主頁",
|
||||
"home.recentlyPlayed": "呢排播左",
|
||||
"home.recentlyAdded": "呢排加嘅",
|
||||
"home.artistsFeed": "藝人動態",
|
||||
"home.artistsFeed.noArtist": "Follow 一啲藝人嚟獲得佢哋嘅最新歌曲資訊。 ",
|
||||
"home.madeForYou": "為你而整",
|
||||
"home.friendsListeningTo": "你啲Friend聽緊",
|
||||
"home.followedArtists": "Follow左嘅藝人",
|
||||
"error.appleMusicSubRequired": "需要訂閱Apple Music先可以用Cider。",
|
||||
"error.connectionError": "連接唔到Apple Music。",
|
||||
"error.noResults": "冇結果。",
|
||||
"error.noResults.description": "重新搵過啦。",
|
||||
"podcast.followOnCider": "喺Cider上Follow",
|
||||
"podcast.followedOnCider": "喺Cider上Follow左",
|
||||
"podcast.subscribeOnItunes": "喺iTunes上訂閱",
|
||||
"podcast.subscribedOnItunes": "喺iTunes上訂閱左",
|
||||
"podcast.itunesStore": "iTunes Store",
|
||||
"podcast.episodes": "單集",
|
||||
"podcast.playEpisode": "播呢集",
|
||||
"podcast.website": "Podcast 網頁",
|
||||
"action.addToLibrary": "加入資料庫",
|
||||
"action.addToLibrary.success": "加入咗資料庫",
|
||||
"action.addToLibrary.error": "加入唔到資料庫",
|
||||
"action.removeFromLibrary": "喺資料庫到刪除",
|
||||
"action.removeFromLibrary.success": "已經喺資料庫到刪除咗",
|
||||
"action.addToQueue": "加入待播清單",
|
||||
"action.addToQueue.success": "加入咗待播清單",
|
||||
"action.addToQueue.error": "加入唔到待播清單",
|
||||
"action.removeFromQueue": "喺待播清單刪除",
|
||||
"action.removeFromQueue.success": "已經喺待播清單到刪除咗",
|
||||
"action.removeFromQueue.error": "喺待播清單到刪除唔到",
|
||||
"action.createPlaylist": "建立新嘅播放清單",
|
||||
"action.addToPlaylist": "加入播放清單",
|
||||
"action.removeFromPlaylist": "喺播放清單到刪除",
|
||||
"action.addToFavorites": "加至收藏",
|
||||
"action.follow": "Follow",
|
||||
"action.follow.success": "Follow緊",
|
||||
"action.follow.error": "Follow唔到",
|
||||
"action.unfollow": "Unfollow",
|
||||
"action.unfollow.success": "Unfollow咗",
|
||||
"action.unfollow.error": "Unfollow唔到",
|
||||
"action.playNext": "下首即刻播",
|
||||
"action.playLater": "陣間先再播",
|
||||
"action.startRadio": "建立電台",
|
||||
"action.goToArtist": "前往藝人",
|
||||
"action.goToAlbum": "前往專輯",
|
||||
"action.moveToTop": "返最頂",
|
||||
"action.share": "分享歌曲",
|
||||
"action.rename": "重新命名",
|
||||
"action.love": "鐘意",
|
||||
"action.unlove": "唔鐘意",
|
||||
"action.dislike": "唔想再睇到",
|
||||
"action.undoDislike": "還原唔想再睇到",
|
||||
"action.showWebRemoteQR": "遙距控制",
|
||||
"action.playTracksNext": "插播 ${app.selectedMediaItems.length} 首歌曲",
|
||||
"action.playTracksLater": "陣間播放 ${app.selectedMediaItems.length} 首歌曲",
|
||||
"action.removeTracks": "喺待播清單到刪除 ${self.selectedItems.length} 首歌曲",
|
||||
"action.import": "匯入",
|
||||
"action.export": "匯出",
|
||||
"action.showAlbum": "顯示完整嘅專輯",
|
||||
"action.tray.minimize": "收埋喺系統托盤",
|
||||
"action.tray.quit": "結束",
|
||||
"action.update": "更新",
|
||||
"action.copy": "複製",
|
||||
"action.newpreset": "新增範本",
|
||||
"action.deletepreset": "刪除範本",
|
||||
"settings.header.general": "一般",
|
||||
"settings.header.general.description": "調整Cider嘅一般設定",
|
||||
"settings.option.general.language": "語言",
|
||||
"settings.option.general.language.main": "語言",
|
||||
"settings.option.general.language.fun": "惡搞語言",
|
||||
"settings.option.general.language.unsorted": "未分類",
|
||||
"settings.header.audio": "音訊",
|
||||
"settings.header.audio.description": "調整Cider嘅音訊設定",
|
||||
"settings.option.audio.quality": "音質",
|
||||
"settings.header.audio.quality.high": "質素優先",
|
||||
"settings.option.audio.seamlessTransition": "無縫播放",
|
||||
"settings.option.audio.enableAdvancedFunctionality": "進階功能",
|
||||
"settings.option.audio.enableAdvancedFunctionality.description": "啟用AudioContext,解鎖類似音量平衡和均衡器嘅進階功能,但係會喺部分電腦造成音樂Lag機。",
|
||||
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "音量平衡",
|
||||
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "平衡輕柔同響亮嘅歌曲,令你有統一嘅聆聽體驗。",
|
||||
"settings.header.visual": "外觀",
|
||||
"settings.header.visual.description": "調整Cider嘅外觀",
|
||||
"settings.option.visual.windowBackgroundStyle": "視窗背景樣式",
|
||||
"settings.header.visual.windowBackgroundStyle.none": "空白",
|
||||
"settings.header.visual.windowBackgroundStyle.artwork": "專輯封面",
|
||||
"settings.header.visual.windowBackgroundStyle.image": "圖片",
|
||||
"settings.option.visual.animatedArtwork": "動態專輯封面",
|
||||
"settings.header.visual.animatedArtwork.always": "總是顯示",
|
||||
"settings.header.visual.animatedArtwork.limited": "淨係喺藝人頁面同專輯封面顯示",
|
||||
"settings.header.visual.animatedArtwork.disable": "熄左佢",
|
||||
"settings.option.visual.animatedArtworkQuality": "動態專輯封面品質",
|
||||
"settings.header.visual.animatedArtworkQuality.low": "低",
|
||||
"settings.header.visual.animatedArtworkQuality.medium": "中",
|
||||
"settings.header.visual.animatedArtworkQuality.high": "高",
|
||||
"settings.header.visual.animatedArtworkQuality.veryHigh": "非常高",
|
||||
"settings.header.visual.animatedArtworkQuality.extreme": "極高",
|
||||
"settings.option.visual.animatedWindowBackground": "動態視窗背景",
|
||||
"settings.option.visual.hardwareAcceleration": "硬體加速",
|
||||
"settings.option.visual.hardwareAcceleration.description": "需要重開Cider先會生效",
|
||||
"settings.header.visual.hardwareAcceleration.default": "預設",
|
||||
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
|
||||
"settings.header.visual.theme": "主題",
|
||||
"settings.option.visual.theme.default": "預設",
|
||||
"settings.option.visual.theme.dark": "黑色",
|
||||
"settings.option.visual.showPersonalInfo": "顯示個人檔案",
|
||||
"settings.header.lyrics": "歌詞",
|
||||
"settings.header.lyrics.description": "調整Cider嘅歌詞設定",
|
||||
"settings.option.lyrics.enableMusixmatch": "啟用 Musixmatch 歌詞",
|
||||
"settings.option.lyrics.enableMusixmatchKaraoke": "開啟唱K模式(僅限Musixmatch)",
|
||||
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌詞語言偏好",
|
||||
"settings.option.lyrics.enableYoutubeLyrics": "播放MV嘅時候用YouTube字幕",
|
||||
"settings.header.connectivity": "外部連結",
|
||||
"settings.header.connectivity.description": "調整Cider同外部嘅連結",
|
||||
"settings.option.connectivity.discordRPC": "Discord 狀態",
|
||||
"settings.option.connectivity.playbackNotifications": "喺播歌嘅時候通知你",
|
||||
"settings.option.connectivity.discordRPC.clearOnPause": "暫停時清除 Discord 狀態",
|
||||
"settings.option.connectivity.lastfmScrobble": "Last.FM Scrobbling 記錄",
|
||||
"settings.option.connectivity.lastfmScrobble.delay": "Last.FM Scrobble 延遲 (%)",
|
||||
"settings.option.connectivity.lastfmScrobble.nowPlaying": "啟用 Last.FM 正在播放",
|
||||
"settings.option.connectivity.lastfmScrobble.removeFeatured": "從歌名中移除藝人推薦 (Last.FM)",
|
||||
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (Last.FM)",
|
||||
"settings.header.experimental": "實驗性功能",
|
||||
"settings.header.experimental.description": "調整Cider嘅實驗性功能",
|
||||
"settings.option.experimental.compactUI": "逼啲既 UI",
|
||||
"settings.option.window.close_button_hide": "㩒交叉制嚟隱藏 Cider",
|
||||
"spatial.notTurnedOn": "唔該喺設定入面啟用左空間音訊先。",
|
||||
"spatial.spatialProperties": "空間音訊屬性",
|
||||
"spatial.width": "幾闊",
|
||||
"spatial.height": "幾高",
|
||||
"spatial.depth": "幾深",
|
||||
"spatial.gain": "增益",
|
||||
"spatial.roomMaterials": "空間材質",
|
||||
"spatial.roomDimensions": "空間大小",
|
||||
"spatial.roomPositions": "空間位置",
|
||||
"spatial.setDimensions": "大小設定",
|
||||
"spatial.setPositions": "位置設定",
|
||||
"spatial.up": "上面",
|
||||
"spatial.front": "前面",
|
||||
"spatial.left": "左邊",
|
||||
"spatial.right": "右邊",
|
||||
"spatial.back": "後面",
|
||||
"spatial.down": "下面",
|
||||
"spatial.listener": "觀眾",
|
||||
"spatial.audioSource": "音源",
|
||||
"settings.header.unfinished": "未搞掂",
|
||||
"remote.web.title": "遙距控制 Cider",
|
||||
"remote.web.description": "Scan 呢個 QR Code 去控制 Cider",
|
||||
"about.thanks": "多謝 Cider Collective 同埋所有合作人作出嘅貢獻。"
|
||||
}
|
||||
{
|
||||
"i18n.languageName": "廣東話(香港)",
|
||||
"i18n.languageNameEnglish": "Cantonese (Hong Kong)",
|
||||
"i18n.category": "main",
|
||||
"i18n.authors": "@tszngaiyip @strikesnc",
|
||||
"app.name": "Cider",
|
||||
"date.format": "${y}年${m}月${d}日",
|
||||
"dialog.cancel": "取消",
|
||||
"dialog.ok": "確認",
|
||||
"notification.updatingLibrarySongs": "更新緊資料庫嘅歌曲...",
|
||||
"notification.updatingLibraryAlbums": "更新緊資料庫嘅專輯...",
|
||||
"notification.updatingLibraryArtists": "更新緊資料庫嘅藝人...",
|
||||
"term.appleInc": "Apple Inc.",
|
||||
"term.appleMusic": "Apple Music",
|
||||
"term.applePodcasts": "Apple Podcasts",
|
||||
"term.itunes": "iTunes",
|
||||
"term.github": "GitHub",
|
||||
"term.discord": "Discord",
|
||||
"term.learnMore": "想知更多",
|
||||
"term.accountSettings": "帳號設定",
|
||||
"term.logout": "登出",
|
||||
"term.login": "登入",
|
||||
"term.about": "關於",
|
||||
"term.privateSession": "無痕模式",
|
||||
"term.queue": "待播清單",
|
||||
"term.history": "播放歷史",
|
||||
"term.search": "搵野",
|
||||
"term.library": "資料庫",
|
||||
"term.listenNow": "即刻聽",
|
||||
"term.browse": "瀏覽",
|
||||
"term.radio": "電台",
|
||||
"term.recentlyAdded": "最近加入",
|
||||
"term.songs": "歌曲",
|
||||
"term.albums": "專輯",
|
||||
"term.artists": "藝人",
|
||||
"term.podcasts": "Podcasts",
|
||||
"term.playlists": "播放清單",
|
||||
"term.playlist": "播放清單",
|
||||
"term.newPlaylist": "新播放清單",
|
||||
"term.newPlaylistFolder": "新資料夾",
|
||||
"term.createNewPlaylist": "新增播放清單",
|
||||
"term.createNewPlaylistFolder": "新增資料夾",
|
||||
"term.deletePlaylist": "你係咪要刪除呢個播放清單?",
|
||||
"term.play": "播放",
|
||||
"term.pause": "暫停",
|
||||
"term.previous": "前一首",
|
||||
"term.next": "下一首",
|
||||
"term.shuffle": "隨機播放",
|
||||
"term.repeat": "重複播放",
|
||||
"term.volume": "音量",
|
||||
"term.mute": "靜音",
|
||||
"term.unmute": "取消靜音",
|
||||
"term.share": "分享",
|
||||
"term.share.success": "複製咗喺剪貼簿",
|
||||
"term.settings": "設定",
|
||||
"term.seeAll": "睇哂全部",
|
||||
"term.sortBy": "排序",
|
||||
"term.sortBy.album": "專輯",
|
||||
"term.sortBy.artist": "藝人",
|
||||
"term.sortBy.name": "歌名",
|
||||
"term.sortBy.genre": "音樂風格",
|
||||
"term.sortBy.releaseDate": "幾時出",
|
||||
"term.sortBy.duration": "幾長",
|
||||
"term.sortOrder": "點排",
|
||||
"term.sortOrder.ascending": "順序",
|
||||
"term.sortOrder.descending": "倒序",
|
||||
"term.viewAs": "想點樣顯示",
|
||||
"term.viewAs.coverArt": "專輯封面",
|
||||
"term.viewAs.list": "列表",
|
||||
"term.size": "大細",
|
||||
"term.size.normal": "正常",
|
||||
"term.size.compact": "迫啲",
|
||||
"term.enable": "開",
|
||||
"term.disable": "熄",
|
||||
"term.enabled": "開左",
|
||||
"term.disabled": "熄左",
|
||||
"term.connect": "連結",
|
||||
"term.connecting": "連緊",
|
||||
"term.disconnect": "取消連結",
|
||||
"term.authed": "授權咗",
|
||||
"term.confirm": "確認?",
|
||||
"term.more": "多啲",
|
||||
"term.less": "少啲",
|
||||
"term.showMore": "顯示多啲",
|
||||
"term.showLess": "顯示少啲",
|
||||
"term.topSongs": "熱門歌曲",
|
||||
"term.latestReleases": "最新出嘅",
|
||||
"term.time.added": "加入於",
|
||||
"term.time.released": "發行於",
|
||||
"term.time.updated": "更新於",
|
||||
"term.time.hours": "粒鐘",
|
||||
"term.time.hour": "粒鐘",
|
||||
"term.time.minutes": "分鐘",
|
||||
"term.time.minute": "分鐘",
|
||||
"term.time.seconds": "秒",
|
||||
"term.time.second": "秒",
|
||||
"term.fullscreenView": "用全螢幕睇",
|
||||
"term.defaultView": "平時咁睇",
|
||||
"term.audioSettings": "音訊設定",
|
||||
"term.clearAll": "清除",
|
||||
"term.recentStations": "呢排聽緊嘅",
|
||||
"term.language": "語言",
|
||||
"term.funLanguages": "惡搞",
|
||||
"term.noLyrics": "搵緊... / 搵唔到歌詞。 / 純音樂黎。",
|
||||
"term.copyright": "版權",
|
||||
"term.rightsReserved": "保留一切權利",
|
||||
"term.sponsor": "課金俾呢個Project",
|
||||
"term.ciderTeam": "Cider 團隊",
|
||||
"term.developer": "開發者",
|
||||
"term.socialTeam": "PR",
|
||||
"term.socials": "我哋嘅社群",
|
||||
"term.contributors": "合作人",
|
||||
"term.equalizer": "均衡器 (EQ)",
|
||||
"term.reset": "重設",
|
||||
"term.tracks": "首歌",
|
||||
"term.videos": "影片",
|
||||
"term.menu": "選項",
|
||||
"term.check": "檢查",
|
||||
"term.aboutArtist": "關於 {{artistName}}",
|
||||
"term.topResult": "熱門搜尋結果",
|
||||
"term.sharedPlaylists": "播放清單",
|
||||
"term.people": "個人檔案",
|
||||
"term.newpreset.name": "新EQ範本嘅名",
|
||||
"term.addedpreset": "新增咗",
|
||||
"term.deletepreset.warn": "你係咪要刪除呢個範本?",
|
||||
"term.deletedpreset": "刪除咗",
|
||||
"term.musicVideos": "MV",
|
||||
"term.stations": "電台",
|
||||
"term.radioShows": "電台單集",
|
||||
"term.recordLabels": "唱片公司",
|
||||
"term.videoExtras": "相關嘅片",
|
||||
"home.title": "主頁",
|
||||
"home.recentlyPlayed": "呢排播左",
|
||||
"home.recentlyAdded": "呢排加嘅",
|
||||
"home.artistsFeed": "藝人動態",
|
||||
"home.artistsFeed.noArtist": "Follow 一啲藝人嚟獲得佢哋嘅最新歌曲資訊。 ",
|
||||
"home.madeForYou": "為你而整",
|
||||
"home.friendsListeningTo": "你啲Friend聽緊",
|
||||
"home.followedArtists": "Follow左嘅藝人",
|
||||
"error.appleMusicSubRequired": "需要訂閱Apple Music先可以用Cider。",
|
||||
"error.connectionError": "連接唔到Apple Music。",
|
||||
"error.noResults": "冇結果。",
|
||||
"error.noResults.description": "重新搵過啦。",
|
||||
"podcast.followOnCider": "喺Cider上Follow",
|
||||
"podcast.followedOnCider": "喺Cider上Follow左",
|
||||
"podcast.subscribeOnItunes": "喺iTunes上訂閱",
|
||||
"podcast.subscribedOnItunes": "喺iTunes上訂閱左",
|
||||
"podcast.itunesStore": "iTunes Store",
|
||||
"podcast.episodes": "單集",
|
||||
"podcast.playEpisode": "播呢集",
|
||||
"podcast.website": "Podcast 網頁",
|
||||
"action.addToLibrary": "加入資料庫",
|
||||
"action.addToLibrary.success": "加入咗資料庫",
|
||||
"action.addToLibrary.error": "加入唔到資料庫",
|
||||
"action.removeFromLibrary": "喺資料庫到刪除",
|
||||
"action.removeFromLibrary.success": "已經喺資料庫到刪除咗",
|
||||
"action.addToQueue": "加入待播清單",
|
||||
"action.addToQueue.success": "加入咗待播清單",
|
||||
"action.addToQueue.error": "加入唔到待播清單",
|
||||
"action.removeFromQueue": "喺待播清單刪除",
|
||||
"action.removeFromQueue.success": "已經喺待播清單到刪除咗",
|
||||
"action.removeFromQueue.error": "喺待播清單到刪除唔到",
|
||||
"action.createPlaylist": "建立新嘅播放清單",
|
||||
"action.addToPlaylist": "加入播放清單",
|
||||
"action.removeFromPlaylist": "喺播放清單到刪除",
|
||||
"action.addToFavorites": "加至收藏",
|
||||
"action.follow": "Follow",
|
||||
"action.follow.success": "Follow緊",
|
||||
"action.follow.error": "Follow唔到",
|
||||
"action.unfollow": "Unfollow",
|
||||
"action.unfollow.success": "Unfollow咗",
|
||||
"action.unfollow.error": "Unfollow唔到",
|
||||
"action.playNext": "下首即刻播",
|
||||
"action.playLater": "陣間先再播",
|
||||
"action.startRadio": "建立電台",
|
||||
"action.goToArtist": "前往藝人",
|
||||
"action.goToAlbum": "前往專輯",
|
||||
"action.moveToTop": "返最頂",
|
||||
"action.share": "分享歌曲",
|
||||
"action.rename": "重新命名",
|
||||
"action.love": "鐘意",
|
||||
"action.unlove": "唔鐘意",
|
||||
"action.dislike": "唔想再睇到",
|
||||
"action.undoDislike": "還原唔想再睇到",
|
||||
"action.showWebRemoteQR": "遙距控制",
|
||||
"action.playTracksNext": "插播 ${app.selectedMediaItems.length} 首歌曲",
|
||||
"action.playTracksLater": "陣間播放 ${app.selectedMediaItems.length} 首歌曲",
|
||||
"action.removeTracks": "喺待播清單到刪除 ${self.selectedItems.length} 首歌曲",
|
||||
"action.import": "匯入",
|
||||
"action.export": "匯出",
|
||||
"action.showAlbum": "顯示完整嘅專輯",
|
||||
"action.tray.minimize": "收埋喺系統托盤",
|
||||
"action.tray.quit": "結束",
|
||||
"action.update": "更新",
|
||||
"action.copy": "複製",
|
||||
"action.newpreset": "新增範本",
|
||||
"action.deletepreset": "刪除範本",
|
||||
"settings.header.general": "一般",
|
||||
"settings.header.general.description": "調整Cider嘅一般設定",
|
||||
"settings.option.general.language": "語言",
|
||||
"settings.option.general.language.main": "語言",
|
||||
"settings.option.general.language.fun": "惡搞語言",
|
||||
"settings.option.general.language.unsorted": "未分類",
|
||||
"settings.header.audio": "音訊",
|
||||
"settings.header.audio.description": "調整Cider嘅音訊設定",
|
||||
"settings.option.audio.quality": "音質",
|
||||
"settings.header.audio.quality.high": "質素優先",
|
||||
"settings.option.audio.seamlessTransition": "無縫播放",
|
||||
"settings.option.audio.enableAdvancedFunctionality": "進階功能",
|
||||
"settings.option.audio.enableAdvancedFunctionality.description": "啟用AudioContext,解鎖類似音量平衡和均衡器嘅進階功能,但係會喺部分電腦造成音樂Lag機。",
|
||||
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "音量平衡",
|
||||
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "平衡輕柔同響亮嘅歌曲,令你有統一嘅聆聽體驗。",
|
||||
"settings.header.visual": "外觀",
|
||||
"settings.header.visual.description": "調整Cider嘅外觀",
|
||||
"settings.option.visual.windowBackgroundStyle": "視窗背景樣式",
|
||||
"settings.header.visual.windowBackgroundStyle.none": "空白",
|
||||
"settings.header.visual.windowBackgroundStyle.artwork": "專輯封面",
|
||||
"settings.header.visual.windowBackgroundStyle.image": "圖片",
|
||||
"settings.option.visual.animatedArtwork": "動態專輯封面",
|
||||
"settings.header.visual.animatedArtwork.always": "總是顯示",
|
||||
"settings.header.visual.animatedArtwork.limited": "淨係喺藝人頁面同專輯封面顯示",
|
||||
"settings.header.visual.animatedArtwork.disable": "熄左佢",
|
||||
"settings.option.visual.animatedArtworkQuality": "動態專輯封面品質",
|
||||
"settings.header.visual.animatedArtworkQuality.low": "低",
|
||||
"settings.header.visual.animatedArtworkQuality.medium": "中",
|
||||
"settings.header.visual.animatedArtworkQuality.high": "高",
|
||||
"settings.header.visual.animatedArtworkQuality.veryHigh": "非常高",
|
||||
"settings.header.visual.animatedArtworkQuality.extreme": "極高",
|
||||
"settings.option.visual.animatedWindowBackground": "動態視窗背景",
|
||||
"settings.option.visual.hardwareAcceleration": "硬體加速",
|
||||
"settings.option.visual.hardwareAcceleration.description": "需要重開Cider先會生效",
|
||||
"settings.header.visual.hardwareAcceleration.default": "預設",
|
||||
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
|
||||
"settings.header.visual.theme": "主題",
|
||||
"settings.option.visual.theme.default": "預設",
|
||||
"settings.option.visual.theme.dark": "黑色",
|
||||
"settings.option.visual.showPersonalInfo": "顯示個人檔案",
|
||||
"settings.header.lyrics": "歌詞",
|
||||
"settings.header.lyrics.description": "調整Cider嘅歌詞設定",
|
||||
"settings.option.lyrics.enableMusixmatch": "啟用 Musixmatch 歌詞",
|
||||
"settings.option.lyrics.enableMusixmatchKaraoke": "開啟唱K模式(僅限Musixmatch)",
|
||||
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌詞語言偏好",
|
||||
"settings.option.lyrics.enableYoutubeLyrics": "播放MV嘅時候用YouTube字幕",
|
||||
"settings.header.connectivity": "外部連結",
|
||||
"settings.header.connectivity.description": "調整Cider同外部嘅連結",
|
||||
"settings.option.connectivity.discordRPC": "Discord 狀態",
|
||||
"settings.option.connectivity.playbackNotifications": "喺播歌嘅時候通知你",
|
||||
"settings.option.connectivity.discordRPC.clearOnPause": "暫停時清除 Discord 狀態",
|
||||
"settings.option.connectivity.lastfmScrobble": "Last.FM Scrobbling 記錄",
|
||||
"settings.option.connectivity.lastfmScrobble.delay": "Last.FM Scrobble 延遲 (%)",
|
||||
"settings.option.connectivity.lastfmScrobble.nowPlaying": "啟用 Last.FM 正在播放",
|
||||
"settings.option.connectivity.lastfmScrobble.removeFeatured": "從歌名中移除藝人推薦 (Last.FM)",
|
||||
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (Last.FM)",
|
||||
"settings.header.experimental": "實驗性功能",
|
||||
"settings.header.experimental.description": "調整Cider嘅實驗性功能",
|
||||
"settings.option.experimental.compactUI": "逼啲既 UI",
|
||||
"settings.option.window.close_button_hide": "㩒交叉制嚟隱藏 Cider",
|
||||
"spatial.notTurnedOn": "唔該喺設定入面啟用左空間音訊先。",
|
||||
"spatial.spatialProperties": "空間音訊屬性",
|
||||
"spatial.width": "幾闊",
|
||||
"spatial.height": "幾高",
|
||||
"spatial.depth": "幾深",
|
||||
"spatial.gain": "增益",
|
||||
"spatial.roomMaterials": "空間材質",
|
||||
"spatial.roomDimensions": "空間大小",
|
||||
"spatial.roomPositions": "空間位置",
|
||||
"spatial.setDimensions": "大小設定",
|
||||
"spatial.setPositions": "位置設定",
|
||||
"spatial.up": "上面",
|
||||
"spatial.front": "前面",
|
||||
"spatial.left": "左邊",
|
||||
"spatial.right": "右邊",
|
||||
"spatial.back": "後面",
|
||||
"spatial.down": "下面",
|
||||
"spatial.listener": "觀眾",
|
||||
"spatial.audioSource": "音源",
|
||||
"settings.header.unfinished": "未搞掂",
|
||||
"remote.web.title": "遙距控制 Cider",
|
||||
"remote.web.description": "Scan 呢個 QR Code 去控制 Cider",
|
||||
"about.thanks": "多謝 Cider Collective 同埋所有合作人作出嘅貢獻。"
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {app, Menu, nativeImage, Tray, ipcMain, clipboard, shell} from 'electron';
|
||||
import {readFileSync} from "fs";
|
||||
import * as path from 'path';
|
||||
import * as log from 'electron-log';
|
||||
import {utils} from './utils';
|
||||
import { app, Menu, nativeImage, Tray, ipcMain, clipboard, shell } from "electron";
|
||||
import { readFileSync } from "fs";
|
||||
import * as path from "path";
|
||||
import * as log from "electron-log";
|
||||
import { utils } from "./utils";
|
||||
|
||||
/**
|
||||
* @file Creates App instance
|
||||
|
@ -11,317 +11,314 @@ import {utils} from './utils';
|
|||
|
||||
/** @namespace */
|
||||
export class AppEvents {
|
||||
private protocols: string[] = [
|
||||
"ame",
|
||||
"cider",
|
||||
"itms",
|
||||
"itmss",
|
||||
"musics",
|
||||
"music"
|
||||
]
|
||||
private plugin: any = undefined;
|
||||
private tray: any = undefined;
|
||||
private i18n: any = undefined;
|
||||
private protocols: string[] = ["ame", "cider", "itms", "itmss", "musics", "music"];
|
||||
private plugin: any = undefined;
|
||||
private tray: any = undefined;
|
||||
private i18n: any = undefined;
|
||||
|
||||
/** @constructor */
|
||||
constructor() {
|
||||
this.start();
|
||||
/** @constructor */
|
||||
constructor() {
|
||||
this.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all actions that occur for the app on start (Mainly commandline arguments)
|
||||
* @returns {void}
|
||||
*/
|
||||
private start(): void {
|
||||
AppEvents.initLogging();
|
||||
console.info("[AppEvents] App started");
|
||||
|
||||
/**********************************************************************************************************************
|
||||
* Startup arguments handling
|
||||
**********************************************************************************************************************/
|
||||
if (app.commandLine.hasSwitch("version") || app.commandLine.hasSwitch("v")) {
|
||||
console.log(app.getVersion());
|
||||
app.exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all actions that occur for the app on start (Mainly commandline arguments)
|
||||
* @returns {void}
|
||||
*/
|
||||
private start(): void {
|
||||
AppEvents.initLogging()
|
||||
console.info('[AppEvents] App started');
|
||||
|
||||
/**********************************************************************************************************************
|
||||
* Startup arguments handling
|
||||
**********************************************************************************************************************/
|
||||
if (app.commandLine.hasSwitch('version') || app.commandLine.hasSwitch('v')) {
|
||||
console.log(app.getVersion())
|
||||
app.exit()
|
||||
}
|
||||
|
||||
// Verbose Check
|
||||
if (app.commandLine.hasSwitch('verbose')) {
|
||||
console.log("[Cider] User has launched the application with --verbose");
|
||||
}
|
||||
|
||||
// Log File Location
|
||||
if (app.commandLine.hasSwitch('log') || app.commandLine.hasSwitch('l')) {
|
||||
console.log(path.join(app.getPath('userData'), 'logs'))
|
||||
app.exit()
|
||||
}
|
||||
|
||||
// Try limiting JS memory to 350MB.
|
||||
app.commandLine.appendSwitch('js-flags', '--max-old-space-size=350');
|
||||
|
||||
// Expose GC
|
||||
app.commandLine.appendSwitch('js-flags', '--expose_gc')
|
||||
|
||||
if (process.platform === "win32") {
|
||||
app.setAppUserModelId(app.getName()) // For notification name
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************
|
||||
* Commandline arguments
|
||||
**********************************************************************************************************************/
|
||||
switch (utils.getStoreValue('visual.hw_acceleration') as string) {
|
||||
default:
|
||||
case "default":
|
||||
app.commandLine.appendSwitch('enable-accelerated-mjpeg-decode')
|
||||
app.commandLine.appendSwitch('enable-accelerated-video')
|
||||
app.commandLine.appendSwitch('disable-gpu-driver-bug-workarounds')
|
||||
app.commandLine.appendSwitch('ignore-gpu-blacklist')
|
||||
app.commandLine.appendSwitch('enable-native-gpu-memory-buffers')
|
||||
app.commandLine.appendSwitch('enable-accelerated-video-decode');
|
||||
app.commandLine.appendSwitch('enable-gpu-rasterization');
|
||||
app.commandLine.appendSwitch('enable-native-gpu-memory-buffers');
|
||||
app.commandLine.appendSwitch('enable-oop-rasterization');
|
||||
break;
|
||||
|
||||
case "webgpu":
|
||||
console.info("WebGPU is enabled.");
|
||||
app.commandLine.appendSwitch('enable-unsafe-webgpu')
|
||||
break;
|
||||
|
||||
case "disabled":
|
||||
console.info("Hardware acceleration is disabled.");
|
||||
app.commandLine.appendSwitch('disable-gpu')
|
||||
break;
|
||||
}
|
||||
|
||||
if (process.platform === "linux") {
|
||||
app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************
|
||||
* Protocols
|
||||
**********************************************************************************************************************/
|
||||
/** */
|
||||
if (process.defaultApp) {
|
||||
if (process.argv.length >= 2) {
|
||||
this.protocols.forEach((protocol: string) => {
|
||||
app.setAsDefaultProtocolClient(protocol, process.execPath, [path.resolve(process.argv[1])])
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.protocols.forEach((protocol: string) => {
|
||||
app.setAsDefaultProtocolClient(protocol)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Verbose Check
|
||||
if (app.commandLine.hasSwitch("verbose")) {
|
||||
console.log("[Cider] User has launched the application with --verbose");
|
||||
}
|
||||
|
||||
public quit() {
|
||||
console.log('[AppEvents] App quit');
|
||||
// Log File Location
|
||||
if (app.commandLine.hasSwitch("log") || app.commandLine.hasSwitch("l")) {
|
||||
console.log(path.join(app.getPath("userData"), "logs"));
|
||||
app.exit();
|
||||
}
|
||||
|
||||
public ready(plug: any) {
|
||||
this.plugin = plug
|
||||
console.log('[AppEvents] App ready');
|
||||
// Try limiting JS memory to 350MB.
|
||||
app.commandLine.appendSwitch("js-flags", "--max-old-space-size=350");
|
||||
|
||||
AppEvents.setLoginSettings()
|
||||
}
|
||||
// Expose GC
|
||||
app.commandLine.appendSwitch("js-flags", "--expose_gc");
|
||||
|
||||
public bwCreated() {
|
||||
app.on('open-url', (event, url) => {
|
||||
event.preventDefault()
|
||||
if (this.protocols.some((protocol: string) => url.includes(protocol))) {
|
||||
this.LinkHandler(url)
|
||||
console.log(url)
|
||||
}
|
||||
})
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
app.setUserActivity('8R23J2835D.com.ciderapp.webremote.play', {
|
||||
title: 'Web Remote',
|
||||
description: 'Connect to your Web Remote',
|
||||
}, "https://webremote.cider.sh")
|
||||
}
|
||||
|
||||
this.InstanceHandler()
|
||||
if (process.platform !== "darwin") {
|
||||
this.InitTray()
|
||||
}
|
||||
if (process.platform === "win32") {
|
||||
app.setAppUserModelId(app.getName()); // For notification name
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************
|
||||
* Private methods
|
||||
* Commandline arguments
|
||||
**********************************************************************************************************************/
|
||||
switch (utils.getStoreValue("visual.hw_acceleration") as string) {
|
||||
default:
|
||||
case "default":
|
||||
app.commandLine.appendSwitch("enable-accelerated-mjpeg-decode");
|
||||
app.commandLine.appendSwitch("enable-accelerated-video");
|
||||
app.commandLine.appendSwitch("disable-gpu-driver-bug-workarounds");
|
||||
app.commandLine.appendSwitch("ignore-gpu-blacklist");
|
||||
app.commandLine.appendSwitch("enable-native-gpu-memory-buffers");
|
||||
app.commandLine.appendSwitch("enable-accelerated-video-decode");
|
||||
app.commandLine.appendSwitch("enable-gpu-rasterization");
|
||||
app.commandLine.appendSwitch("enable-native-gpu-memory-buffers");
|
||||
app.commandLine.appendSwitch("enable-oop-rasterization");
|
||||
break;
|
||||
|
||||
/**
|
||||
* Handles links (URI) and protocols for the application
|
||||
* @param arg
|
||||
*/
|
||||
private LinkHandler(arg: string) {
|
||||
if (!arg) return;
|
||||
case "webgpu":
|
||||
console.info("WebGPU is enabled.");
|
||||
app.commandLine.appendSwitch("enable-unsafe-webgpu");
|
||||
break;
|
||||
|
||||
// LastFM Auth URL
|
||||
if (arg.includes('auth')) {
|
||||
const authURI = arg.split('/auth/')[1]
|
||||
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
|
||||
console.log('token: ', authURI.split('lastfm?token=')[1])
|
||||
utils.getWindow().webContents.executeJavaScript(`ipcRenderer.send('lastfm:auth', "${authURI.split('lastfm?token=')[1]}")`).catch(console.error)
|
||||
}
|
||||
case "disabled":
|
||||
console.info("Hardware acceleration is disabled.");
|
||||
app.commandLine.appendSwitch("disable-gpu");
|
||||
break;
|
||||
}
|
||||
|
||||
if (process.platform === "linux") {
|
||||
app.commandLine.appendSwitch("disable-features", "MediaSessionService");
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************
|
||||
* Protocols
|
||||
**********************************************************************************************************************/
|
||||
/** */
|
||||
if (process.defaultApp) {
|
||||
if (process.argv.length >= 2) {
|
||||
this.protocols.forEach((protocol: string) => {
|
||||
app.setAsDefaultProtocolClient(protocol, process.execPath, [path.resolve(process.argv[1])]);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.protocols.forEach((protocol: string) => {
|
||||
app.setAsDefaultProtocolClient(protocol);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public quit() {
|
||||
console.log("[AppEvents] App quit");
|
||||
}
|
||||
|
||||
public ready(plug: any) {
|
||||
this.plugin = plug;
|
||||
console.log("[AppEvents] App ready");
|
||||
|
||||
AppEvents.setLoginSettings();
|
||||
}
|
||||
|
||||
public bwCreated() {
|
||||
app.on("open-url", (event, url) => {
|
||||
event.preventDefault();
|
||||
if (this.protocols.some((protocol: string) => url.includes(protocol))) {
|
||||
this.LinkHandler(url);
|
||||
console.log(url);
|
||||
}
|
||||
});
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
app.setUserActivity(
|
||||
"8R23J2835D.com.ciderapp.webremote.play",
|
||||
{
|
||||
title: "Web Remote",
|
||||
description: "Connect to your Web Remote",
|
||||
},
|
||||
"https://webremote.cider.sh"
|
||||
);
|
||||
}
|
||||
|
||||
this.InstanceHandler();
|
||||
if (process.platform !== "darwin") {
|
||||
this.InitTray();
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************
|
||||
* Private methods
|
||||
**********************************************************************************************************************/
|
||||
|
||||
/**
|
||||
* Handles links (URI) and protocols for the application
|
||||
* @param arg
|
||||
*/
|
||||
private LinkHandler(arg: string) {
|
||||
if (!arg) return;
|
||||
|
||||
// LastFM Auth URL
|
||||
if (arg.includes("auth")) {
|
||||
const authURI = arg.split("/auth/")[1];
|
||||
if (authURI.startsWith("lastfm")) {
|
||||
// If we wanted more auth options
|
||||
console.log("token: ", authURI.split("lastfm?token=")[1]);
|
||||
utils
|
||||
.getWindow()
|
||||
.webContents.executeJavaScript(`ipcRenderer.send('lastfm:auth', "${authURI.split("lastfm?token=")[1]}")`)
|
||||
.catch(console.error);
|
||||
}
|
||||
} else if (arg.includes("playpause")) {
|
||||
//language=JS
|
||||
utils.getWindow().webContents.executeJavaScript("MusicKitInterop.playPause()");
|
||||
} else if (arg.includes("nextitem")) {
|
||||
//language=JS
|
||||
utils.getWindow().webContents.executeJavaScript("app.mk.skipToNextItem()");
|
||||
}
|
||||
// Play
|
||||
else if (arg.includes("/play/")) {
|
||||
//Steer away from protocol:// specific conditionals
|
||||
const playParam = arg.split("/play/")[1];
|
||||
|
||||
const mediaType = {
|
||||
"s/": "song",
|
||||
"a/": "album",
|
||||
"p/": "playlist",
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(mediaType)) {
|
||||
if (playParam.includes(key)) {
|
||||
const id = playParam.split(key)[1];
|
||||
utils.getWindow().webContents.send("play", value, id);
|
||||
console.debug(`[LinkHandler] Attempting to load ${value} by id: ${id}`);
|
||||
}
|
||||
else if (arg.includes('playpause')) {
|
||||
//language=JS
|
||||
utils.getWindow().webContents.executeJavaScript('MusicKitInterop.playPause()')
|
||||
}
|
||||
else if (arg.includes('nextitem')) {
|
||||
//language=JS
|
||||
utils.getWindow().webContents.executeJavaScript('app.mk.skipToNextItem()')
|
||||
}
|
||||
// Play
|
||||
else if (arg.includes('/play/')) { //Steer away from protocol:// specific conditionals
|
||||
const playParam = arg.split('/play/')[1]
|
||||
}
|
||||
} else if (arg.includes("music.apple.com")) {
|
||||
// URL (used with itms/itmss/music/musics uris)
|
||||
console.log(arg);
|
||||
let url = arg.split("//")[1];
|
||||
console.warn(`[LinkHandler] Attempting to load url: ${url}`);
|
||||
utils.getWindow().webContents.send("play", "url", url);
|
||||
} else if (arg.includes("/debug/appdata")) {
|
||||
shell.openPath(app.getPath("userData"));
|
||||
} else if (arg.includes("/debug/logs")) {
|
||||
shell.openPath(app.getPath("logs"));
|
||||
} else if (arg.includes("/discord")) {
|
||||
shell.openExternal("https://discord.gg/applemusic");
|
||||
} else if (arg.includes("/github")) {
|
||||
shell.openExternal("https://github.com/ciderapp/cider");
|
||||
} else if (arg.includes("/donate")) {
|
||||
shell.openExternal("https://opencollective.com/ciderapp");
|
||||
} else if (arg.includes("/beep")) {
|
||||
shell.beep();
|
||||
} else {
|
||||
utils.getWindow().webContents.executeJavaScript(`app.appRoute('${arg.split("//")[1]}')`);
|
||||
}
|
||||
}
|
||||
|
||||
const mediaType = {
|
||||
"s/": "song",
|
||||
"a/": "album",
|
||||
"p/": "playlist"
|
||||
}
|
||||
/**
|
||||
* Handles the creation of a new instance of the app
|
||||
*/
|
||||
private InstanceHandler() {
|
||||
// Detects of an existing instance is running (So if the lock has been achieved, no existing instance has been found)
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
for (const [key, value] of Object.entries(mediaType)) {
|
||||
if (playParam.includes(key)) {
|
||||
const id = playParam.split(key)[1]
|
||||
utils.getWindow().webContents.send('play', value, id)
|
||||
console.debug(`[LinkHandler] Attempting to load ${value} by id: ${id}`)
|
||||
}
|
||||
}
|
||||
if (!gotTheLock) {
|
||||
// Runs on the new instance if another instance has been found
|
||||
console.log("[Cider] Another instance has been found, quitting.");
|
||||
app.quit();
|
||||
} else {
|
||||
// Runs on the first instance if no other instance has been found
|
||||
app.on("second-instance", (_event, startArgs) => {
|
||||
console.log("[InstanceHandler] (second-instance) Instance started with " + startArgs.toString());
|
||||
|
||||
} else if (arg.includes('music.apple.com')) { // URL (used with itms/itmss/music/musics uris)
|
||||
console.log(arg)
|
||||
let url = arg.split('//')[1]
|
||||
console.warn(`[LinkHandler] Attempting to load url: ${url}`);
|
||||
utils.getWindow().webContents.send('play', 'url', url)
|
||||
} else if (arg.includes('/debug/appdata')) {
|
||||
shell.openPath(app.getPath('userData'))
|
||||
} else if (arg.includes('/debug/logs')) {
|
||||
shell.openPath(app.getPath('logs'))
|
||||
} else if (arg.includes('/discord')) {
|
||||
shell.openExternal('https://discord.gg/applemusic')
|
||||
} else if (arg.includes('/github')) {
|
||||
shell.openExternal('https://github.com/ciderapp/cider')
|
||||
} else if (arg.includes('/donate')) {
|
||||
shell.openExternal('https://opencollective.com/ciderapp')
|
||||
} else if (arg.includes('/beep')) {
|
||||
shell.beep()
|
||||
startArgs.forEach((arg) => {
|
||||
console.log(arg);
|
||||
if (arg.includes("cider://")) {
|
||||
console.debug("[InstanceHandler] (second-instance) Link detected with " + arg);
|
||||
this.LinkHandler(arg);
|
||||
} else if (arg.includes("--force-quit")) {
|
||||
console.warn("[InstanceHandler] (second-instance) Force Quit found. Quitting App.");
|
||||
app.quit();
|
||||
} else if (utils.getWindow()) {
|
||||
if (utils.getWindow().isMinimized()) utils.getWindow().restore();
|
||||
utils.getWindow().show();
|
||||
utils.getWindow().focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the applications tray
|
||||
*/
|
||||
private InitTray() {
|
||||
const icons = {
|
||||
win32: nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.ico`)).resize({
|
||||
width: 32,
|
||||
height: 32,
|
||||
}),
|
||||
linux: nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
|
||||
width: 32,
|
||||
height: 32,
|
||||
}),
|
||||
darwin: nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
|
||||
width: 20,
|
||||
height: 20,
|
||||
}),
|
||||
};
|
||||
this.tray = new Tray(process.platform === "win32" ? icons.win32 : process.platform === "darwin" ? icons.darwin : icons.linux);
|
||||
this.tray.setToolTip(app.getName());
|
||||
this.setTray(false);
|
||||
|
||||
this.tray.on("double-click", () => {
|
||||
if (utils.getWindow()) {
|
||||
if (utils.getWindow().isVisible()) {
|
||||
utils.getWindow().focus();
|
||||
} else {
|
||||
utils.getWindow().webContents.executeJavaScript(`app.appRoute('${arg.split('//')[1]}')`)
|
||||
utils.getWindow().show();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles the creation of a new instance of the app
|
||||
*/
|
||||
private InstanceHandler() {
|
||||
// Detects of an existing instance is running (So if the lock has been achieved, no existing instance has been found)
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
utils.getWindow().on("show", () => {
|
||||
this.setTray(true);
|
||||
});
|
||||
|
||||
if (!gotTheLock) { // Runs on the new instance if another instance has been found
|
||||
console.log('[Cider] Another instance has been found, quitting.')
|
||||
app.quit()
|
||||
} else { // Runs on the first instance if no other instance has been found
|
||||
app.on('second-instance', (_event, startArgs) => {
|
||||
console.log("[InstanceHandler] (second-instance) Instance started with " + startArgs.toString())
|
||||
utils.getWindow().on("restore", () => {
|
||||
this.setTray(true);
|
||||
});
|
||||
|
||||
startArgs.forEach(arg => {
|
||||
console.log(arg)
|
||||
if (arg.includes("cider://")) {
|
||||
console.debug('[InstanceHandler] (second-instance) Link detected with ' + arg)
|
||||
this.LinkHandler(arg)
|
||||
} else if (arg.includes("--force-quit")) {
|
||||
console.warn('[InstanceHandler] (second-instance) Force Quit found. Quitting App.');
|
||||
app.quit()
|
||||
} else if (utils.getWindow()) {
|
||||
if (utils.getWindow().isMinimized()) utils.getWindow().restore()
|
||||
utils.getWindow().show()
|
||||
utils.getWindow().focus()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
utils.getWindow().on("hide", () => {
|
||||
this.setTray(false);
|
||||
});
|
||||
|
||||
}
|
||||
utils.getWindow().on("minimize", () => {
|
||||
this.setTray(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the applications tray
|
||||
*/
|
||||
private InitTray() {
|
||||
const icons = {
|
||||
"win32": nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.ico`)).resize({
|
||||
width: 32,
|
||||
height: 32
|
||||
}),
|
||||
"linux": nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
|
||||
width: 32,
|
||||
height: 32
|
||||
}),
|
||||
"darwin": nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
|
||||
width: 20,
|
||||
height: 20
|
||||
}),
|
||||
}
|
||||
this.tray = new Tray(process.platform === 'win32' ? icons.win32 : (process.platform === 'darwin' ? icons.darwin : icons.linux))
|
||||
this.tray.setToolTip(app.getName())
|
||||
this.setTray(false)
|
||||
/**
|
||||
* Sets the tray context menu to a given state
|
||||
* @param visible - BrowserWindow Visibility
|
||||
*/
|
||||
private setTray(visible: boolean = utils.getWindow().isVisible()) {
|
||||
this.i18n = utils.getLocale(utils.getStoreValue("general.language"));
|
||||
|
||||
this.tray.on('double-click', () => {
|
||||
if (utils.getWindow()) {
|
||||
if (utils.getWindow().isVisible()) {
|
||||
utils.getWindow().focus()
|
||||
} else {
|
||||
utils.getWindow().show()
|
||||
}
|
||||
}
|
||||
})
|
||||
const ciderIcon = nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
|
||||
width: 24,
|
||||
height: 24,
|
||||
});
|
||||
|
||||
utils.getWindow().on('show', () => {
|
||||
this.setTray(true)
|
||||
})
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: app.getName(),
|
||||
enabled: false,
|
||||
icon: ciderIcon,
|
||||
},
|
||||
|
||||
utils.getWindow().on('restore', () => {
|
||||
this.setTray(true)
|
||||
})
|
||||
{ type: "separator" },
|
||||
|
||||
utils.getWindow().on('hide', () => {
|
||||
this.setTray(false)
|
||||
})
|
||||
|
||||
utils.getWindow().on('minimize', () => {
|
||||
this.setTray(false)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tray context menu to a given state
|
||||
* @param visible - BrowserWindow Visibility
|
||||
*/
|
||||
private setTray(visible: boolean = utils.getWindow().isVisible()) {
|
||||
this.i18n = utils.getLocale(utils.getStoreValue('general.language'))
|
||||
|
||||
const ciderIcon = nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
|
||||
width: 24,
|
||||
height: 24
|
||||
})
|
||||
|
||||
const menu = Menu.buildFromTemplate([
|
||||
|
||||
{
|
||||
label: app.getName(),
|
||||
enabled: false,
|
||||
icon: ciderIcon,
|
||||
|
||||
},
|
||||
|
||||
{type: 'separator'},
|
||||
|
||||
/* For now only idea i dont know if posible to implement
|
||||
/* For now only idea i dont know if posible to implement
|
||||
|
||||
this could be implemented in a plugin if you would like track info, it would be impractical to put listeners in this file. -Core
|
||||
{
|
||||
|
@ -337,90 +334,93 @@ export class AppEvents {
|
|||
|
||||
{type: 'separator'},
|
||||
*/
|
||||
|
||||
{
|
||||
visible: !visible,
|
||||
label: this.i18n['term.playpause'],
|
||||
click: () => {
|
||||
utils.getWindow().webContents.executeJavaScript('MusicKitInterop.playPause()')
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
visible: !visible,
|
||||
label: this.i18n['term.next'],
|
||||
click: () => {
|
||||
utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.next()`)
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
visible: !visible,
|
||||
label: this.i18n['term.previous'],
|
||||
click: () => {
|
||||
utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.previous()`)
|
||||
}
|
||||
},
|
||||
|
||||
{type: 'separator', visible: !visible},
|
||||
{
|
||||
visible: !visible,
|
||||
label: this.i18n["term.playpause"],
|
||||
click: () => {
|
||||
utils.getWindow().webContents.executeJavaScript("MusicKitInterop.playPause()");
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
label: (visible ? this.i18n['action.tray.minimize'] : `${this.i18n['action.tray.show']}`),
|
||||
click: () => {
|
||||
if (utils.getWindow()) {
|
||||
if (visible) {
|
||||
utils.getWindow().hide()
|
||||
} else {
|
||||
utils.getWindow().show()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: this.i18n['term.quit'],
|
||||
click: () => {
|
||||
app.quit()
|
||||
}
|
||||
{
|
||||
visible: !visible,
|
||||
label: this.i18n["term.next"],
|
||||
click: () => {
|
||||
utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.next()`);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
visible: !visible,
|
||||
label: this.i18n["term.previous"],
|
||||
click: () => {
|
||||
utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.previous()`);
|
||||
},
|
||||
},
|
||||
|
||||
{ type: "separator", visible: !visible },
|
||||
|
||||
{
|
||||
label: visible ? this.i18n["action.tray.minimize"] : `${this.i18n["action.tray.show"]}`,
|
||||
click: () => {
|
||||
if (utils.getWindow()) {
|
||||
if (visible) {
|
||||
utils.getWindow().hide();
|
||||
} else {
|
||||
utils.getWindow().show();
|
||||
}
|
||||
])
|
||||
this.tray.setContextMenu(menu)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.i18n["term.quit"],
|
||||
click: () => {
|
||||
app.quit();
|
||||
},
|
||||
},
|
||||
]);
|
||||
this.tray.setContextMenu(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes logging in the application
|
||||
* @private
|
||||
*/
|
||||
private static initLogging() {
|
||||
log.transports.console.format = '[{h}:{i}:{s}.{ms}] [{level}] {text}';
|
||||
Object.assign(console, log.functions);
|
||||
console.debug = function(...args: any[]) {
|
||||
if (!app.isPackaged) {
|
||||
log.debug(...args)
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Initializes logging in the application
|
||||
* @private
|
||||
*/
|
||||
private static initLogging() {
|
||||
log.transports.console.format = "[{h}:{i}:{s}.{ms}] [{level}] {text}";
|
||||
Object.assign(console, log.functions);
|
||||
console.debug = function (...args: any[]) {
|
||||
if (!app.isPackaged) {
|
||||
log.debug(...args);
|
||||
}
|
||||
};
|
||||
|
||||
ipcMain.on('fetch-log', (_event) => {
|
||||
const data = readFileSync(log.transports.file.getFile().path, {encoding: 'utf8', flag: 'r'});
|
||||
clipboard.writeText(data)
|
||||
})
|
||||
}
|
||||
ipcMain.on("fetch-log", (_event) => {
|
||||
const data = readFileSync(log.transports.file.getFile().path, {
|
||||
encoding: "utf8",
|
||||
flag: "r",
|
||||
});
|
||||
clipboard.writeText(data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set login settings
|
||||
* @private
|
||||
*/
|
||||
private static setLoginSettings() {
|
||||
if (utils.getStoreValue('general.onStartup.enabled')) {
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: true,
|
||||
path: app.getPath('exe'),
|
||||
args: [`${utils.getStoreValue('general.onStartup.hidden') ? '--hidden' : ''}`]
|
||||
})
|
||||
} else {
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: false,
|
||||
path: app.getPath('exe')
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Set login settings
|
||||
* @private
|
||||
*/
|
||||
private static setLoginSettings() {
|
||||
if (utils.getStoreValue("general.onStartup.enabled")) {
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: true,
|
||||
path: app.getPath("exe"),
|
||||
args: [`${utils.getStoreValue("general.onStartup.hidden") ? "--hidden" : ""}`],
|
||||
});
|
||||
} else {
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: false,
|
||||
path: app.getPath("exe"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,10 @@
|
|||
var util = require('util');
|
||||
var castv2Cli = require('castv2-client');
|
||||
var util = require("util");
|
||||
var castv2Cli = require("castv2-client");
|
||||
var RequestResponseController = castv2Cli.RequestResponseController;
|
||||
|
||||
function CiderCastController(client, sourceId, destinationId) {
|
||||
RequestResponseController.call(this, client, sourceId, destinationId, 'urn:x-cast:com.ciderapp.customdata');
|
||||
this.once('close', onclose);
|
||||
RequestResponseController.call(this, client, sourceId, destinationId, "urn:x-cast:com.ciderapp.customdata");
|
||||
this.once("close", onclose);
|
||||
var self = this;
|
||||
function onclose() {
|
||||
self.stop();
|
||||
|
@ -13,20 +13,20 @@ function CiderCastController(client, sourceId, destinationId) {
|
|||
|
||||
util.inherits(CiderCastController, RequestResponseController);
|
||||
|
||||
CiderCastController.prototype.sendIp = function(ip) {
|
||||
CiderCastController.prototype.sendIp = function (ip) {
|
||||
// TODO: Implement Callback
|
||||
let data = {
|
||||
ip : ip
|
||||
}
|
||||
ip: ip,
|
||||
};
|
||||
this.request(data);
|
||||
};
|
||||
|
||||
CiderCastController.prototype.kill = function() {
|
||||
CiderCastController.prototype.kill = function () {
|
||||
// TODO: Implement Callback
|
||||
let data = {
|
||||
action : "stop"
|
||||
}
|
||||
action: "stop",
|
||||
};
|
||||
this.request(data);
|
||||
};
|
||||
|
||||
module.exports = CiderCastController;
|
||||
module.exports = CiderCastController;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
//@ts-nocheck
|
||||
var util = require('util');
|
||||
var util = require("util");
|
||||
// var debug = require('debug')('castv2-client');
|
||||
var Application = require('castv2-client').Application;
|
||||
var MediaController = require('castv2-client').MediaController;
|
||||
var CiderCastController = require('./castcontroller');
|
||||
var Application = require("castv2-client").Application;
|
||||
var MediaController = require("castv2-client").MediaController;
|
||||
var CiderCastController = require("./castcontroller");
|
||||
|
||||
function CiderReceiver(client, session) {
|
||||
Application.apply(this, arguments);
|
||||
|
@ -11,70 +11,69 @@ function CiderReceiver(client, session) {
|
|||
this.media = this.createController(MediaController);
|
||||
this.mediaReceiver = this.createController(CiderCastController);
|
||||
|
||||
this.media.on('status', onstatus);
|
||||
this.media.on("status", onstatus);
|
||||
|
||||
var self = this;
|
||||
|
||||
function onstatus(status) {
|
||||
self.emit('status', status);
|
||||
self.emit("status", status);
|
||||
}
|
||||
|
||||
}
|
||||
// FE96A351
|
||||
// 27E1334F
|
||||
CiderReceiver.APP_ID = 'FE96A351';
|
||||
CiderReceiver.APP_ID = "FE96A351";
|
||||
|
||||
util.inherits(CiderReceiver, Application);
|
||||
|
||||
CiderReceiver.prototype.getStatus = function(callback) {
|
||||
CiderReceiver.prototype.getStatus = function (callback) {
|
||||
this.media.getStatus.apply(this.media, arguments);
|
||||
};
|
||||
|
||||
CiderReceiver.prototype.load = function(media, options, callback) {
|
||||
CiderReceiver.prototype.load = function (media, options, callback) {
|
||||
this.media.load.apply(this.media, arguments);
|
||||
};
|
||||
|
||||
CiderReceiver.prototype.play = function(callback) {
|
||||
CiderReceiver.prototype.play = function (callback) {
|
||||
this.media.play.apply(this.media, arguments);
|
||||
};
|
||||
|
||||
CiderReceiver.prototype.pause = function(callback) {
|
||||
CiderReceiver.prototype.pause = function (callback) {
|
||||
this.media.pause.apply(this.media, arguments);
|
||||
};
|
||||
|
||||
CiderReceiver.prototype.stop = function(callback) {
|
||||
CiderReceiver.prototype.stop = function (callback) {
|
||||
this.media.stop.apply(this.media, arguments);
|
||||
};
|
||||
|
||||
CiderReceiver.prototype.seek = function(currentTime, callback) {
|
||||
CiderReceiver.prototype.seek = function (currentTime, callback) {
|
||||
this.media.seek.apply(this.media, arguments);
|
||||
};
|
||||
|
||||
CiderReceiver.prototype.queueLoad = function(items, options, callback) {
|
||||
CiderReceiver.prototype.queueLoad = function (items, options, callback) {
|
||||
this.media.queueLoad.apply(this.media, arguments);
|
||||
};
|
||||
|
||||
CiderReceiver.prototype.queueInsert = function(items, options, callback) {
|
||||
CiderReceiver.prototype.queueInsert = function (items, options, callback) {
|
||||
this.media.queueInsert.apply(this.media, arguments);
|
||||
};
|
||||
|
||||
CiderReceiver.prototype.queueRemove = function(itemIds, options, callback) {
|
||||
CiderReceiver.prototype.queueRemove = function (itemIds, options, callback) {
|
||||
this.media.queueRemove.apply(this.media, arguments);
|
||||
};
|
||||
|
||||
CiderReceiver.prototype.queueReorder = function(itemIds, options, callback) {
|
||||
CiderReceiver.prototype.queueReorder = function (itemIds, options, callback) {
|
||||
this.media.queueReorder.apply(this.media, arguments);
|
||||
};
|
||||
|
||||
CiderReceiver.prototype.queueUpdate = function(items, callback) {
|
||||
CiderReceiver.prototype.queueUpdate = function (items, callback) {
|
||||
this.media.queueUpdate.apply(this.media, arguments);
|
||||
};
|
||||
|
||||
CiderReceiver.prototype.sendIp = function(opts){
|
||||
CiderReceiver.prototype.sendIp = function (opts) {
|
||||
this.mediaReceiver.sendIp.apply(this.mediaReceiver, arguments);
|
||||
};
|
||||
|
||||
CiderReceiver.prototype.kill = function(opts){
|
||||
CiderReceiver.prototype.kill = function (opts) {
|
||||
this.mediaReceiver.kill.apply(this.mediaReceiver, arguments);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as electron from 'electron'
|
||||
import {utils} from './utils';
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as electron from "electron";
|
||||
import { utils } from "./utils";
|
||||
|
||||
//
|
||||
// Hello, this is our loader for the various plugins that the Cider Development Team built for our
|
||||
|
@ -16,108 +16,105 @@ import {utils} from './utils';
|
|||
* @see {@link https://github.com/ciderapp/Cider/wiki/Plugins|Documentation}
|
||||
*/
|
||||
export class Plugins {
|
||||
private static PluginMap: any = {};
|
||||
private basePluginsPath = path.join(__dirname, '../plugins');
|
||||
private userPluginsPath = path.join(electron.app.getPath('userData'), 'Plugins');
|
||||
private readonly pluginsList: any = {};
|
||||
private static PluginMap: any = {};
|
||||
private basePluginsPath = path.join(__dirname, "../plugins");
|
||||
private userPluginsPath = path.join(electron.app.getPath("userData"), "Plugins");
|
||||
private readonly pluginsList: any = {};
|
||||
|
||||
constructor() {
|
||||
this.pluginsList = this.getPlugins();
|
||||
constructor() {
|
||||
this.pluginsList = this.getPlugins();
|
||||
}
|
||||
|
||||
public static getPluginFromMap(plugin: string): any {
|
||||
if (Plugins.PluginMap[plugin]) {
|
||||
return Plugins.PluginMap[plugin];
|
||||
} else {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
|
||||
public getPlugins(): any {
|
||||
let plugins: any = {};
|
||||
|
||||
if (fs.existsSync(this.basePluginsPath)) {
|
||||
fs.readdirSync(this.basePluginsPath).forEach((file) => {
|
||||
if (file.endsWith(".ts") || file.endsWith(".js")) {
|
||||
const plugin = require(path.join(this.basePluginsPath, file)).default;
|
||||
if (plugins[file] || plugin.name in plugins) {
|
||||
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||
} else {
|
||||
plugins[file] = new plugin(utils);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getPluginFromMap(plugin: string): any {
|
||||
if (Plugins.PluginMap[plugin]) {
|
||||
return Plugins.PluginMap[plugin];
|
||||
} else {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
|
||||
public getPlugins(): any {
|
||||
let plugins: any = {};
|
||||
|
||||
|
||||
if (fs.existsSync(this.basePluginsPath)) {
|
||||
fs.readdirSync(this.basePluginsPath).forEach(file => {
|
||||
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
||||
const plugin = require(path.join(this.basePluginsPath, file)).default;
|
||||
if (plugins[file] || plugin.name in plugins) {
|
||||
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||
} else {
|
||||
plugins[file] = new plugin(utils);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (fs.existsSync(this.userPluginsPath)) {
|
||||
fs.readdirSync(this.userPluginsPath).forEach(file => {
|
||||
// Plugins V1
|
||||
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
||||
if (!electron.app.isPackaged) {
|
||||
const plugin = require(path.join(this.userPluginsPath, file)).default;
|
||||
file = file.replace('.ts', '').replace('.js', '');
|
||||
if (plugins[file] || plugin in plugins) {
|
||||
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||
} else {
|
||||
plugins[file] = new plugin(utils);
|
||||
}
|
||||
} else {
|
||||
const plugin = require(path.join(this.userPluginsPath, file));
|
||||
file = file.replace('.ts', '').replace('.js', '');
|
||||
if (plugins[file] || plugin in plugins) {
|
||||
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||
} else {
|
||||
plugins[file] = new plugin(utils);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Plugins V2
|
||||
else if (fs.lstatSync(path.join(this.userPluginsPath, file)).isDirectory()) {
|
||||
const pluginPath = path.join(this.userPluginsPath, file);
|
||||
if (fs.existsSync(path.join(pluginPath, 'package.json'))) {
|
||||
const pluginPackage = require(path.join(pluginPath, "package.json"));
|
||||
const plugin = require(path.join(pluginPath, pluginPackage.main));
|
||||
if (plugins[plugin.name] || plugin.name in plugins) {
|
||||
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||
} else {
|
||||
Plugins.PluginMap[pluginPackage.name] = file;
|
||||
const pluginEnv = {
|
||||
app: electron.app,
|
||||
store: utils.getStore(),
|
||||
utils: utils,
|
||||
win: utils.getWindow(),
|
||||
dir: pluginPath,
|
||||
dirName: file
|
||||
}
|
||||
plugins[plugin.name] = new plugin(pluginEnv);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log('[PluginHandler] Loaded plugins:', Object.keys(plugins));
|
||||
return plugins;
|
||||
}
|
||||
|
||||
public callPlugins(event: string, ...args: any[]) {
|
||||
for (const plugin in this.pluginsList) {
|
||||
if (this.pluginsList[plugin][event]) {
|
||||
try {
|
||||
this.pluginsList[plugin][event](...args);
|
||||
} catch (e) {
|
||||
console.error(`[${plugin}] An error was encountered: ${e}`);
|
||||
console.error(e)
|
||||
}
|
||||
if (fs.existsSync(this.userPluginsPath)) {
|
||||
fs.readdirSync(this.userPluginsPath).forEach((file) => {
|
||||
// Plugins V1
|
||||
if (file.endsWith(".ts") || file.endsWith(".js")) {
|
||||
if (!electron.app.isPackaged) {
|
||||
const plugin = require(path.join(this.userPluginsPath, file)).default;
|
||||
file = file.replace(".ts", "").replace(".js", "");
|
||||
if (plugins[file] || plugin in plugins) {
|
||||
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||
} else {
|
||||
plugins[file] = new plugin(utils);
|
||||
}
|
||||
} else {
|
||||
const plugin = require(path.join(this.userPluginsPath, file));
|
||||
file = file.replace(".ts", "").replace(".js", "");
|
||||
if (plugins[file] || plugin in plugins) {
|
||||
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||
} else {
|
||||
plugins[file] = new plugin(utils);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public callPlugin(plugin: string, event: string, ...args: any[]) {
|
||||
if (this.pluginsList[plugin][event]) {
|
||||
this.pluginsList[plugin][event](...args);
|
||||
// Plugins V2
|
||||
else if (fs.lstatSync(path.join(this.userPluginsPath, file)).isDirectory()) {
|
||||
const pluginPath = path.join(this.userPluginsPath, file);
|
||||
if (fs.existsSync(path.join(pluginPath, "package.json"))) {
|
||||
const pluginPackage = require(path.join(pluginPath, "package.json"));
|
||||
const plugin = require(path.join(pluginPath, pluginPackage.main));
|
||||
if (plugins[plugin.name] || plugin.name in plugins) {
|
||||
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||
} else {
|
||||
Plugins.PluginMap[pluginPackage.name] = file;
|
||||
const pluginEnv = {
|
||||
app: electron.app,
|
||||
store: utils.getStore(),
|
||||
utils: utils,
|
||||
win: utils.getWindow(),
|
||||
dir: pluginPath,
|
||||
dirName: file,
|
||||
};
|
||||
plugins[plugin.name] = new plugin(pluginEnv);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log("[PluginHandler] Loaded plugins:", Object.keys(plugins));
|
||||
return plugins;
|
||||
}
|
||||
|
||||
public callPlugins(event: string, ...args: any[]) {
|
||||
for (const plugin in this.pluginsList) {
|
||||
if (this.pluginsList[plugin][event]) {
|
||||
try {
|
||||
this.pluginsList[plugin][event](...args);
|
||||
} catch (e) {
|
||||
console.error(`[${plugin}] An error was encountered: ${e}`);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public callPlugin(plugin: string, event: string, ...args: any[]) {
|
||||
if (this.pluginsList[plugin][event]) {
|
||||
this.pluginsList[plugin][event](...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,374 +1,320 @@
|
|||
import * as ElectronStore from 'electron-store';
|
||||
import * as ElectronStore from "electron-store";
|
||||
import * as electron from "electron";
|
||||
import {app} from "electron";
|
||||
import { app } from "electron";
|
||||
import fetch from "electron-fetch";
|
||||
|
||||
export class Store {
|
||||
static cfg: ElectronStore;
|
||||
static cfg: ElectronStore;
|
||||
|
||||
private defaults: any = {
|
||||
"main": {
|
||||
"PLATFORM": process.platform,
|
||||
"UPDATABLE": app.isPackaged && (!process.mas || !process.windowsStore || !process.env.FLATPAK_ID)
|
||||
private defaults: any = {
|
||||
main: {
|
||||
PLATFORM: process.platform,
|
||||
UPDATABLE: app.isPackaged && (!process.mas || !process.windowsStore || !process.env.FLATPAK_ID),
|
||||
},
|
||||
general: {
|
||||
close_button_hide: false,
|
||||
language: "en_US", // electron.app.getLocale().replace('-', '_') this can be used in future
|
||||
playbackNotifications: true,
|
||||
resumeOnStartupBehavior: "local",
|
||||
privateEnabled: false,
|
||||
themeUpdateNotification: true,
|
||||
sidebarItems: {
|
||||
recentlyAdded: true,
|
||||
songs: true,
|
||||
albums: true,
|
||||
artists: true,
|
||||
videos: true,
|
||||
podcasts: true,
|
||||
},
|
||||
sidebarCollapsed: {
|
||||
cider: false,
|
||||
applemusic: false,
|
||||
library: false,
|
||||
amplaylists: false,
|
||||
playlists: false,
|
||||
localLibrary: false,
|
||||
},
|
||||
onStartup: {
|
||||
enabled: false,
|
||||
hidden: false,
|
||||
},
|
||||
resumeTabs: {
|
||||
tab: "home",
|
||||
dynamicData: "",
|
||||
},
|
||||
keybindings: {
|
||||
search: ["CommandOrControl", "F"],
|
||||
listnow: ["CommandOrControl", "L"],
|
||||
browse: ["CommandOrControl", "B"],
|
||||
recentAdd: ["CommandOrControl", "G"],
|
||||
songs: ["CommandOrControl", "J"],
|
||||
albums: ["CommandOrControl", process.platform == "darwin" ? "Option" : process.platform == "linux" ? "Shift" : "Alt", "A"],
|
||||
artists: ["CommandOrControl", "D"],
|
||||
togglePrivateSession: ["CommandOrControl", "P"],
|
||||
webRemote: ["CommandOrControl", process.platform == "darwin" ? "Option" : process.platform == "linux" ? "Shift" : "Alt", "W"],
|
||||
audioSettings: ["CommandOrControl", process.platform == "darwin" ? "Option" : process.platform == "linux" ? "Shift" : "Alt", "A"],
|
||||
pluginMenu: ["CommandOrControl", process.platform == "darwin" ? "Option" : process.platform == "linux" ? "Shift" : "Alt", "P"],
|
||||
castToDevices: ["CommandOrControl", process.platform == "darwin" ? "Option" : process.platform == "linux" ? "Shift" : "Alt", "C"],
|
||||
settings: [
|
||||
"CommandOrControl", // Who the hell uses a different key for this? Fucking Option?
|
||||
",",
|
||||
],
|
||||
zoomn: ["Control", "numadd"],
|
||||
zoomt: ["Control", "numsub"],
|
||||
zoomrst: ["Control", "num0"],
|
||||
openDeveloperTools: ["CommandOrControl", "Shift", "I"],
|
||||
},
|
||||
showLovedTracksInline: true,
|
||||
},
|
||||
connectivity: {
|
||||
discord_rpc: {
|
||||
enabled: true,
|
||||
client: "Cider",
|
||||
clear_on_pause: true,
|
||||
hide_buttons: false,
|
||||
hide_timestamp: false,
|
||||
state_format: "by {artist}",
|
||||
details_format: "{title}",
|
||||
},
|
||||
lastfm: {
|
||||
enabled: false,
|
||||
scrobble_after: 50,
|
||||
filter_loop: false,
|
||||
filter_types: {},
|
||||
remove_featured: false,
|
||||
secrets: {
|
||||
username: "",
|
||||
key: "",
|
||||
},
|
||||
"general": {
|
||||
"close_button_hide": false,
|
||||
"language": "en_US", // electron.app.getLocale().replace('-', '_') this can be used in future
|
||||
"playbackNotifications": true,
|
||||
"resumeOnStartupBehavior": "local",
|
||||
"privateEnabled": false,
|
||||
"themeUpdateNotification": true,
|
||||
"sidebarItems": {
|
||||
"recentlyAdded": true,
|
||||
"songs": true,
|
||||
"albums": true,
|
||||
"artists": true,
|
||||
"videos": true,
|
||||
"podcasts": true
|
||||
},
|
||||
"sidebarCollapsed": {
|
||||
"cider": false,
|
||||
"applemusic": false,
|
||||
"library": false,
|
||||
"amplaylists": false,
|
||||
"playlists": false,
|
||||
"localLibrary": false
|
||||
},
|
||||
"onStartup": {
|
||||
"enabled": false,
|
||||
"hidden": false,
|
||||
},
|
||||
"resumeTabs": {
|
||||
"tab": "home",
|
||||
"dynamicData": ""
|
||||
},
|
||||
"keybindings": {
|
||||
"search": [
|
||||
"CommandOrControl",
|
||||
"F"
|
||||
],
|
||||
"listnow": [
|
||||
"CommandOrControl",
|
||||
"L"
|
||||
],
|
||||
"browse": [
|
||||
"CommandOrControl",
|
||||
"B"
|
||||
],
|
||||
"recentAdd": [
|
||||
"CommandOrControl",
|
||||
"G"
|
||||
],
|
||||
"songs": [
|
||||
"CommandOrControl",
|
||||
"J"
|
||||
],
|
||||
"albums": [
|
||||
"CommandOrControl",
|
||||
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift" : "Alt"),
|
||||
"A"
|
||||
],
|
||||
"artists": [
|
||||
"CommandOrControl",
|
||||
"D"
|
||||
],
|
||||
"togglePrivateSession": [
|
||||
"CommandOrControl",
|
||||
"P"
|
||||
],
|
||||
"webRemote": [
|
||||
"CommandOrControl",
|
||||
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift" : "Alt"),
|
||||
"W"
|
||||
],
|
||||
"audioSettings": [
|
||||
"CommandOrControl",
|
||||
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift" : "Alt"),
|
||||
"A"
|
||||
],
|
||||
"pluginMenu": [
|
||||
"CommandOrControl",
|
||||
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift" : "Alt"),
|
||||
"P"
|
||||
],
|
||||
"castToDevices": [
|
||||
"CommandOrControl",
|
||||
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift" : "Alt"),
|
||||
"C"
|
||||
],
|
||||
"settings": [
|
||||
"CommandOrControl", // Who the hell uses a different key for this? Fucking Option?
|
||||
","
|
||||
],
|
||||
"zoomn": [
|
||||
"Control",
|
||||
"numadd",
|
||||
],
|
||||
"zoomt": [
|
||||
"Control",
|
||||
"numsub",
|
||||
],
|
||||
"zoomrst": [
|
||||
"Control",
|
||||
"num0",
|
||||
],
|
||||
"openDeveloperTools": [
|
||||
"CommandOrControl",
|
||||
"Shift",
|
||||
"I"
|
||||
]
|
||||
},
|
||||
"showLovedTracksInline": true
|
||||
},
|
||||
},
|
||||
home: {
|
||||
followedArtists: [],
|
||||
favoriteItems: [],
|
||||
},
|
||||
libraryPrefs: {
|
||||
songs: {
|
||||
scroll: "paged",
|
||||
sort: "name",
|
||||
sortOrder: "asc",
|
||||
size: "normal",
|
||||
},
|
||||
albums: {
|
||||
scroll: "paged",
|
||||
sort: "name",
|
||||
sortOrder: "asc",
|
||||
viewAs: "covers",
|
||||
},
|
||||
playlists: {
|
||||
scroll: "infinite",
|
||||
},
|
||||
localPaths: [],
|
||||
pageSize: 250,
|
||||
},
|
||||
audio: {
|
||||
volume: 1,
|
||||
volumeStep: 0.05,
|
||||
maxVolume: 1,
|
||||
lastVolume: 1,
|
||||
muted: false,
|
||||
playbackRate: 1,
|
||||
quality: "HIGH",
|
||||
seamless_audio: true,
|
||||
normalization: true,
|
||||
dBSPL: false,
|
||||
dBSPLcalibration: 90,
|
||||
maikiwiAudio: {
|
||||
ciderPPE: true,
|
||||
ciderPPE_value: "MAIKIWI",
|
||||
opportunisticCorrection_state: "OFF",
|
||||
atmosphereRealizer1: false,
|
||||
atmosphereRealizer1_value: "NATURAL_STANDARD",
|
||||
atmosphereRealizer2: false,
|
||||
atmosphereRealizer2_value: "NATURAL_STANDARD",
|
||||
spatial: false,
|
||||
spatialProfile: "BPLK",
|
||||
vibrantBass: {
|
||||
// Hard coded into the app. Don't include any of this config into exporting presets in store.ts
|
||||
frequencies: [17.182, 42.169, 53.763, 112.69, 119.65, 264.59, 336.57, 400.65, 505.48, 612.7, 838.7, 1155.3, 1175.6, 3406.8, 5158.6, 5968.1, 6999.9, 7468.6, 8862.9, 9666, 10109],
|
||||
Q: [2.5, 0.388, 5, 5, 2.5, 7.071, 14.14, 10, 7.071, 14.14, 8.409, 0.372, 7.071, 10, 16.82, 7.071, 28.28, 20, 8.409, 40, 40],
|
||||
gain: [-0.34, 2.49, 0.23, -0.49, 0.23, -0.12, 0.32, -0.29, 0.33, 0.19, -0.18, -1.27, -0.11, 0.25, -0.18, -0.53, 0.34, 1.32, 1.78, 0.41, -0.28],
|
||||
},
|
||||
"connectivity": {
|
||||
"discord_rpc": {
|
||||
"enabled": true,
|
||||
"client": "Cider",
|
||||
"clear_on_pause": true,
|
||||
"hide_buttons": false,
|
||||
"hide_timestamp": false,
|
||||
"state_format": "by {artist}",
|
||||
"details_format": "{title}",
|
||||
},
|
||||
"lastfm": {
|
||||
"enabled": false,
|
||||
"scrobble_after": 50,
|
||||
"filter_loop": false,
|
||||
"filter_types": {},
|
||||
"remove_featured": false,
|
||||
"secrets": {
|
||||
"username": "",
|
||||
"key": ""
|
||||
}
|
||||
},
|
||||
spatial: false,
|
||||
spatial_properties: {
|
||||
presets: [],
|
||||
gain: 0.8,
|
||||
listener_position: [0, 0, 0],
|
||||
audio_position: [0, 0, 0],
|
||||
room_dimensions: {
|
||||
width: 32,
|
||||
height: 12,
|
||||
depth: 32,
|
||||
},
|
||||
room_materials: {
|
||||
left: "metal",
|
||||
right: "metal",
|
||||
front: "brick-bare",
|
||||
back: "brick-bare",
|
||||
down: "acoustic-ceiling-tiles",
|
||||
up: "acoustic-ceiling-tiles",
|
||||
},
|
||||
},
|
||||
equalizer: {
|
||||
preset: "default",
|
||||
frequencies: [32, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
|
||||
gain: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
Q: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
mix: 1,
|
||||
vibrantBass: 0,
|
||||
presets: [],
|
||||
userGenerated: false,
|
||||
},
|
||||
},
|
||||
visual: {
|
||||
theme: "",
|
||||
styles: [],
|
||||
scrollbars: 0, // 0 = show on hover, 2 = always hide, 3 = always show
|
||||
refresh_rate: 0,
|
||||
window_background_style: "none", // "none", "artwork", "color"
|
||||
animated_artwork: "limited", // 0 = always, 1 = limited, 2 = never
|
||||
animated_artwork_qualityLevel: 1,
|
||||
bg_artwork_rotation: false,
|
||||
hw_acceleration: "default", // default, webgpu, disabled
|
||||
showuserinfo: true,
|
||||
transparent: false,
|
||||
miniplayer_top_toggle: true,
|
||||
directives: {
|
||||
windowLayout: "default",
|
||||
},
|
||||
windowControlPosition: 0, // 0 default right
|
||||
nativeTitleBar: false,
|
||||
windowColor: "#000000",
|
||||
customAccentColor: false,
|
||||
accentColor: "#fc3c44",
|
||||
purplePodcastPlaybackBar: false,
|
||||
maxElementScale: -1, // -1 default, anything else is a custom scale
|
||||
},
|
||||
lyrics: {
|
||||
enable_mxm: true,
|
||||
mxm_karaoke: false,
|
||||
mxm_language: "disabled",
|
||||
enable_qq: false,
|
||||
enable_yt: false,
|
||||
},
|
||||
advanced: {
|
||||
AudioContext: true,
|
||||
experiments: [],
|
||||
playlistTrackMapping: true,
|
||||
ffmpegLocation: "",
|
||||
disableLogging: true,
|
||||
},
|
||||
connectUser: {
|
||||
auth: null,
|
||||
sync: {
|
||||
themes: false,
|
||||
plugins: false,
|
||||
settings: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
private migrations: any = {};
|
||||
private schema: ElectronStore.Schema<any> = {
|
||||
"connectivity.discord_rpc": {
|
||||
type: "object",
|
||||
},
|
||||
};
|
||||
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"followedArtists": [],
|
||||
"favoriteItems": []
|
||||
},
|
||||
"libraryPrefs": {
|
||||
"songs": {
|
||||
"scroll": "paged",
|
||||
"sort": "name",
|
||||
"sortOrder": "asc",
|
||||
"size": "normal"
|
||||
},
|
||||
"albums": {
|
||||
"scroll": "paged",
|
||||
"sort": "name",
|
||||
"sortOrder": "asc",
|
||||
"viewAs": "covers"
|
||||
},
|
||||
"playlists": {
|
||||
"scroll": "infinite"
|
||||
},
|
||||
"localPaths": [],
|
||||
"pageSize": 250
|
||||
},
|
||||
"audio": {
|
||||
"volume": 1,
|
||||
"volumeStep": 0.05,
|
||||
"maxVolume": 1,
|
||||
"lastVolume": 1,
|
||||
"muted": false,
|
||||
"playbackRate": 1,
|
||||
"quality": "HIGH",
|
||||
"seamless_audio": true,
|
||||
"normalization": true,
|
||||
"dBSPL": false,
|
||||
"dBSPLcalibration": 90,
|
||||
"maikiwiAudio": {
|
||||
"ciderPPE": true,
|
||||
"ciderPPE_value": "MAIKIWI",
|
||||
"opportunisticCorrection_state": "OFF",
|
||||
"atmosphereRealizer1": false,
|
||||
"atmosphereRealizer1_value": "NATURAL_STANDARD",
|
||||
"atmosphereRealizer2": false,
|
||||
"atmosphereRealizer2_value": "NATURAL_STANDARD",
|
||||
"spatial": false,
|
||||
"spatialProfile": "BPLK",
|
||||
"vibrantBass": { // Hard coded into the app. Don't include any of this config into exporting presets in store.ts
|
||||
'frequencies': [17.182, 42.169, 53.763, 112.69, 119.65, 264.59, 336.57, 400.65, 505.48, 612.7, 838.7, 1155.3, 1175.6, 3406.8, 5158.6, 5968.1, 6999.9, 7468.6, 8862.9, 9666, 10109],
|
||||
'Q': [2.5, 0.388, 5, 5, 2.5, 7.071, 14.14, 10, 7.071, 14.14, 8.409, 0.372, 7.071, 10, 16.82, 7.071, 28.28, 20, 8.409, 40, 40],
|
||||
'gain': [-0.34, 2.49, 0.23, -0.49, 0.23, -0.12, 0.32, -0.29, 0.33, 0.19, -0.18, -1.27, -0.11, 0.25, -0.18, -0.53, 0.34, 1.32, 1.78, 0.41, -0.28]
|
||||
}
|
||||
},
|
||||
"spatial": false,
|
||||
"spatial_properties": {
|
||||
"presets": [],
|
||||
"gain": 0.8,
|
||||
"listener_position": [0, 0, 0],
|
||||
"audio_position": [0, 0, 0],
|
||||
"room_dimensions": {
|
||||
"width": 32,
|
||||
"height": 12,
|
||||
"depth": 32
|
||||
},
|
||||
"room_materials": {
|
||||
"left": 'metal',
|
||||
"right": 'metal',
|
||||
"front": 'brick-bare',
|
||||
"back": 'brick-bare',
|
||||
"down": 'acoustic-ceiling-tiles',
|
||||
"up": 'acoustic-ceiling-tiles',
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
'preset': "default",
|
||||
'frequencies': [32, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
|
||||
'gain': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
'Q': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
'mix': 1,
|
||||
'vibrantBass': 0,
|
||||
'presets': [],
|
||||
'userGenerated': false
|
||||
},
|
||||
},
|
||||
"visual": {
|
||||
"theme": "",
|
||||
"styles": [],
|
||||
"scrollbars": 0, // 0 = show on hover, 2 = always hide, 3 = always show
|
||||
"refresh_rate": 0,
|
||||
"window_background_style": "none", // "none", "artwork", "color"
|
||||
"animated_artwork": "limited", // 0 = always, 1 = limited, 2 = never
|
||||
"animated_artwork_qualityLevel": 1,
|
||||
"bg_artwork_rotation": false,
|
||||
"hw_acceleration": "default", // default, webgpu, disabled
|
||||
"showuserinfo": true,
|
||||
"transparent": false,
|
||||
"miniplayer_top_toggle": true,
|
||||
"directives": {
|
||||
"windowLayout": "default"
|
||||
},
|
||||
"windowControlPosition": 0, // 0 default right
|
||||
"nativeTitleBar": false,
|
||||
"windowColor": "#000000",
|
||||
"customAccentColor": false,
|
||||
"accentColor": "#fc3c44",
|
||||
"purplePodcastPlaybackBar": false,
|
||||
"maxElementScale": -1 // -1 default, anything else is a custom scale
|
||||
},
|
||||
"lyrics": {
|
||||
"enable_mxm": true,
|
||||
"mxm_karaoke": false,
|
||||
"mxm_language": "disabled",
|
||||
"enable_qq": false,
|
||||
"enable_yt": false,
|
||||
},
|
||||
"advanced": {
|
||||
"AudioContext": true,
|
||||
"experiments": [],
|
||||
"playlistTrackMapping": true,
|
||||
"ffmpegLocation": "",
|
||||
"disableLogging": true
|
||||
},
|
||||
"connectUser": {
|
||||
"auth": null,
|
||||
"sync": {
|
||||
themes: false,
|
||||
plugins: false,
|
||||
settings: false,
|
||||
}
|
||||
},
|
||||
constructor() {
|
||||
Store.cfg = new ElectronStore({
|
||||
name: "cider-config",
|
||||
defaults: this.defaults,
|
||||
schema: this.schema,
|
||||
migrations: this.migrations,
|
||||
clearInvalidConfig: false, //disabled for now
|
||||
});
|
||||
|
||||
Store.cfg.set(this.mergeStore(this.defaults, Store.cfg.store));
|
||||
this.ipcHandler();
|
||||
}
|
||||
|
||||
static pushToCloud(): void {
|
||||
if (Store.cfg.get("connectUser.auth") === null) return;
|
||||
var syncData = Object();
|
||||
if (Store.cfg.get("connectUser.sync.themes")) {
|
||||
syncData.push({
|
||||
themes: Store.cfg.store.themes,
|
||||
});
|
||||
}
|
||||
private migrations: any = {}
|
||||
private schema: ElectronStore.Schema<any> = {
|
||||
"connectivity.discord_rpc": {
|
||||
type: 'object'
|
||||
},
|
||||
if (Store.cfg.get("connectUser.sync.plugins")) {
|
||||
syncData.push({
|
||||
plugins: Store.cfg.store.plugins,
|
||||
});
|
||||
}
|
||||
|
||||
constructor() {
|
||||
Store.cfg = new ElectronStore({
|
||||
name: 'cider-config',
|
||||
defaults: this.defaults,
|
||||
schema: this.schema,
|
||||
migrations: this.migrations,
|
||||
clearInvalidConfig: false //disabled for now
|
||||
});
|
||||
|
||||
Store.cfg.set(this.mergeStore(this.defaults, Store.cfg.store))
|
||||
this.ipcHandler();
|
||||
if (Store.cfg.get("connectUser.sync.settings")) {
|
||||
syncData.push({
|
||||
general: Store.cfg.get("general"),
|
||||
home: Store.cfg.get("home"),
|
||||
libraryPrefs: Store.cfg.get("libraryPrefs"),
|
||||
advanced: Store.cfg.get("advanced"),
|
||||
});
|
||||
}
|
||||
let postBody = {
|
||||
id: Store.cfg.get("connectUser.id"),
|
||||
app: electron.app.getName(),
|
||||
version: electron.app.isPackaged ? electron.app.getVersion() : "dev",
|
||||
syncData: syncData,
|
||||
};
|
||||
|
||||
static pushToCloud(): void {
|
||||
if (Store.cfg.get('connectUser.auth') === null) return;
|
||||
var syncData = Object();
|
||||
if (Store.cfg.get('connectUser.sync.themes')) {
|
||||
syncData.push({
|
||||
themes: Store.cfg.store.themes
|
||||
})
|
||||
}
|
||||
if (Store.cfg.get('connectUser.sync.plugins')) {
|
||||
syncData.push({
|
||||
plugins: Store.cfg.store.plugins
|
||||
})
|
||||
}
|
||||
fetch("https://connect.cidercollective.dev/api/v1/setttings/set", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(postBody),
|
||||
});
|
||||
}
|
||||
|
||||
if (Store.cfg.get('connectUser.sync.settings')) {
|
||||
syncData.push({
|
||||
general: Store.cfg.get('general'),
|
||||
home: Store.cfg.get('home'),
|
||||
libraryPrefs: Store.cfg.get('libraryPrefs'),
|
||||
advanced: Store.cfg.get('advanced'),
|
||||
})
|
||||
}
|
||||
let postBody = {
|
||||
id: Store.cfg.get('connectUser.id'),
|
||||
app: electron.app.getName(),
|
||||
version: electron.app.isPackaged ? electron.app.getVersion() : 'dev',
|
||||
syncData: syncData
|
||||
}
|
||||
|
||||
fetch('https://connect.cidercollective.dev/api/v1/setttings/set', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(postBody)
|
||||
})
|
||||
/**
|
||||
* Merge Configurations
|
||||
* @param target The target configuration
|
||||
* @param source The source configuration
|
||||
*/
|
||||
private mergeStore = (target: { [x: string]: any }, source: { [x: string]: any }) => {
|
||||
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
|
||||
for (const key of Object.keys(source)) {
|
||||
if (key.includes("migrations")) {
|
||||
continue;
|
||||
}
|
||||
if (source[key] instanceof Array) {
|
||||
continue;
|
||||
}
|
||||
if (source[key] instanceof Object) Object.assign(source[key], this.mergeStore(target[key], source[key]));
|
||||
}
|
||||
// Join `target` and modified `source`
|
||||
Object.assign(target || {}, source);
|
||||
return target;
|
||||
};
|
||||
|
||||
/**
|
||||
* Merge Configurations
|
||||
* @param target The target configuration
|
||||
* @param source The source configuration
|
||||
*/
|
||||
private mergeStore = (target: { [x: string]: any; }, source: { [x: string]: any; }) => {
|
||||
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
|
||||
for (const key of Object.keys(source)) {
|
||||
if (key.includes('migrations')) {
|
||||
continue;
|
||||
}
|
||||
if (source[key] instanceof Array) {
|
||||
continue
|
||||
}
|
||||
if (source[key] instanceof Object) Object.assign(source[key], this.mergeStore(target[key], source[key]))
|
||||
}
|
||||
// Join `target` and modified `source`
|
||||
Object.assign(target || {}, source)
|
||||
return target
|
||||
}
|
||||
/**
|
||||
* IPC Handler
|
||||
*/
|
||||
private ipcHandler(): void {
|
||||
electron.ipcMain.handle("getStoreValue", (_event, key, defaultValue) => {
|
||||
return defaultValue ? Store.cfg.get(key, true) : Store.cfg.get(key);
|
||||
});
|
||||
|
||||
/**
|
||||
* IPC Handler
|
||||
*/
|
||||
private ipcHandler(): void {
|
||||
electron.ipcMain.handle('getStoreValue', (_event, key, defaultValue) => {
|
||||
return (defaultValue ? Store.cfg.get(key, true) : Store.cfg.get(key));
|
||||
});
|
||||
electron.ipcMain.handle("setStoreValue", (_event, key, value) => {
|
||||
Store.cfg.set(key, value);
|
||||
});
|
||||
|
||||
electron.ipcMain.handle('setStoreValue', (_event, key, value) => {
|
||||
Store.cfg.set(key, value);
|
||||
});
|
||||
electron.ipcMain.on("getStore", (event) => {
|
||||
event.returnValue = Store.cfg.store;
|
||||
});
|
||||
|
||||
electron.ipcMain.on('getStore', (event) => {
|
||||
event.returnValue = Store.cfg.store
|
||||
})
|
||||
|
||||
electron.ipcMain.on('setStore', (_event, store) => {
|
||||
Store.cfg.store = store
|
||||
})
|
||||
}
|
||||
electron.ipcMain.on("setStore", (_event, store) => {
|
||||
Store.cfg.store = store;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,97 +1,96 @@
|
|||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import {Store} from "./store";
|
||||
import {BrowserWindow as bw} from "./browserwindow";
|
||||
import {app, BrowserWindow, ipcMain} from "electron";
|
||||
import { Store } from "./store";
|
||||
import { BrowserWindow as bw } from "./browserwindow";
|
||||
import { app, BrowserWindow, ipcMain } from "electron";
|
||||
import fetch from "electron-fetch";
|
||||
import ElectronStore from "electron-store";
|
||||
|
||||
export class utils {
|
||||
/**
|
||||
* Playback Functions
|
||||
*/
|
||||
static playback = {
|
||||
pause: () => {
|
||||
bw.win.webContents.executeJavaScript("MusicKitInterop.pause()");
|
||||
},
|
||||
play: () => {
|
||||
bw.win.webContents.executeJavaScript("MusicKitInterop.play()");
|
||||
},
|
||||
playPause: () => {
|
||||
bw.win.webContents.executeJavaScript("MusicKitInterop.playPause()");
|
||||
},
|
||||
next: () => {
|
||||
bw.win.webContents.executeJavaScript("MusicKitInterop.next()");
|
||||
},
|
||||
previous: () => {
|
||||
bw.win.webContents.executeJavaScript("MusicKitInterop.previous()");
|
||||
},
|
||||
seek: (seconds: number) => {
|
||||
bw.win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${seconds})`);
|
||||
},
|
||||
};
|
||||
/**
|
||||
* Paths for the application to use
|
||||
*/
|
||||
static paths: any = {
|
||||
srcPath: path.join(__dirname, "../../src"),
|
||||
rendererPath: path.join(__dirname, "../../src/renderer"),
|
||||
mainPath: path.join(__dirname, "../../src/main"),
|
||||
resourcePath: path.join(__dirname, "../../resources"),
|
||||
i18nPath: path.join(__dirname, "../../src/i18n"),
|
||||
i18nPathSrc: path.join(__dirname, "../../src/il8n/source"),
|
||||
ciderCache: path.resolve(app.getPath("userData"), "CiderCache"),
|
||||
themes: path.resolve(app.getPath("userData"), "Themes"),
|
||||
plugins: path.resolve(app.getPath("userData"), "Plugins"),
|
||||
externals: path.resolve(app.getPath("userData"), "externals"),
|
||||
};
|
||||
|
||||
/**
|
||||
* Playback Functions
|
||||
*/
|
||||
static playback = {
|
||||
pause: () => {
|
||||
bw.win.webContents.executeJavaScript("MusicKitInterop.pause()")
|
||||
},
|
||||
play: () => {
|
||||
bw.win.webContents.executeJavaScript("MusicKitInterop.play()")
|
||||
},
|
||||
playPause: () => {
|
||||
bw.win.webContents.executeJavaScript("MusicKitInterop.playPause()")
|
||||
},
|
||||
next: () => {
|
||||
bw.win.webContents.executeJavaScript("MusicKitInterop.next()")
|
||||
},
|
||||
previous: () => {
|
||||
bw.win.webContents.executeJavaScript("MusicKitInterop.previous()")
|
||||
},
|
||||
seek: (seconds: number) => {
|
||||
bw.win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${seconds})`)
|
||||
}
|
||||
/**
|
||||
* Get the path
|
||||
* @returns {string}
|
||||
* @param name
|
||||
*/
|
||||
static getPath(name: string): string {
|
||||
return this.paths[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the app
|
||||
* @returns {Electron.App}
|
||||
*/
|
||||
static getApp(): Electron.App {
|
||||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IPCMain
|
||||
*/
|
||||
static getIPCMain(): Electron.IpcMain {
|
||||
return ipcMain;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the Express instance
|
||||
* @returns {any}
|
||||
*/
|
||||
static getExpress(): any {
|
||||
return bw.express;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the i18n locale for the given language.
|
||||
* @param language {string} The language to fetch the locale for.
|
||||
* @param key {string} The key to search for.
|
||||
* @returns {string | Object} The locale value.
|
||||
*/
|
||||
static getLocale(language: string, key?: string): string | object {
|
||||
let i18n: { [index: string]: Object } = JSON.parse(fs.readFileSync(path.join(this.paths.i18nPath, "en_US.json"), "utf8"));
|
||||
|
||||
if (language !== "en_US" && fs.existsSync(path.join(this.paths.i18nPath, `${language}.json`))) {
|
||||
i18n = Object.assign(i18n, JSON.parse(fs.readFileSync(path.join(this.paths.i18nPath, `${language}.json`), "utf8")));
|
||||
}
|
||||
/**
|
||||
* Paths for the application to use
|
||||
*/
|
||||
static paths: any = {
|
||||
srcPath: path.join(__dirname, "../../src"),
|
||||
rendererPath: path.join(__dirname, "../../src/renderer"),
|
||||
mainPath: path.join(__dirname, "../../src/main"),
|
||||
resourcePath: path.join(__dirname, "../../resources"),
|
||||
i18nPath: path.join(__dirname, "../../src/i18n"),
|
||||
i18nPathSrc: path.join(__dirname, "../../src/il8n/source"),
|
||||
ciderCache: path.resolve(app.getPath("userData"), "CiderCache"),
|
||||
themes: path.resolve(app.getPath("userData"), "Themes"),
|
||||
plugins: path.resolve(app.getPath("userData"), "Plugins"),
|
||||
externals: path.resolve(app.getPath("userData"), "externals"),
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the path
|
||||
* @returns {string}
|
||||
* @param name
|
||||
*/
|
||||
static getPath(name: string): string {
|
||||
return this.paths[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the app
|
||||
* @returns {Electron.App}
|
||||
*/
|
||||
static getApp(): Electron.App {
|
||||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IPCMain
|
||||
*/
|
||||
static getIPCMain(): Electron.IpcMain {
|
||||
return ipcMain
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the Express instance
|
||||
* @returns {any}
|
||||
*/
|
||||
static getExpress(): any {
|
||||
return bw.express
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the i18n locale for the given language.
|
||||
* @param language {string} The language to fetch the locale for.
|
||||
* @param key {string} The key to search for.
|
||||
* @returns {string | Object} The locale value.
|
||||
*/
|
||||
static getLocale(language: string, key?: string): string | object {
|
||||
let i18n: { [index: string]: Object } = JSON.parse(fs.readFileSync(path.join(this.paths.i18nPath, "en_US.json"), "utf8"));
|
||||
|
||||
if (language !== "en_US" && fs.existsSync(path.join(this.paths.i18nPath, `${language}.json`))) {
|
||||
i18n = Object.assign(i18n, JSON.parse(fs.readFileSync(path.join(this.paths.i18nPath, `${language}.json`), "utf8")));
|
||||
}
|
||||
/* else if (!fs.existsSync(path.join(this.paths.i18nPath, `${language}.json`))) {
|
||||
/* else if (!fs.existsSync(path.join(this.paths.i18nPath, `${language}.json`))) {
|
||||
fetch(`https://raw.githubusercontent.com/ciderapp/Cider/main/src/i18n/${language}.json`)
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
|
@ -103,72 +102,70 @@ export class utils {
|
|||
}
|
||||
})
|
||||
} */
|
||||
|
||||
if (key) {
|
||||
return i18n[key]
|
||||
} else {
|
||||
return i18n
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a store value
|
||||
* @param key
|
||||
* @returns store value
|
||||
*/
|
||||
static getStoreValue(key: string): any {
|
||||
return Store.cfg.get(key)
|
||||
if (key) {
|
||||
return i18n[key];
|
||||
} else {
|
||||
return i18n;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a store
|
||||
* @returns store
|
||||
*/
|
||||
static getStore(): Object {
|
||||
return Store.cfg.store
|
||||
/**
|
||||
* Gets a store value
|
||||
* @param key
|
||||
* @returns store value
|
||||
*/
|
||||
static getStoreValue(key: string): any {
|
||||
return Store.cfg.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a store
|
||||
* @returns store
|
||||
*/
|
||||
static getStore(): Object {
|
||||
return Store.cfg.store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the store instance
|
||||
* @returns {Store}
|
||||
*/
|
||||
static getStoreInstance(): ElectronStore {
|
||||
return Store.cfg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a store value
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
static setStoreValue(key: string, value: any): void {
|
||||
Store.cfg.set(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes Store to Connect
|
||||
* @return Function
|
||||
*/
|
||||
static pushStoreToConnect(): Function {
|
||||
return Store.pushToCloud;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the browser window
|
||||
*/
|
||||
static getWindow(): Electron.BrowserWindow {
|
||||
if (bw.win) {
|
||||
return bw.win;
|
||||
} else {
|
||||
return BrowserWindow.getAllWindows()[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the store instance
|
||||
* @returns {Store}
|
||||
*/
|
||||
static getStoreInstance(): ElectronStore {
|
||||
return Store.cfg
|
||||
}
|
||||
static loadPluginFrontend(path: string): void {}
|
||||
|
||||
/**
|
||||
* Sets a store value
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
static setStoreValue(key: string, value: any): void {
|
||||
Store.cfg.set(key, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes Store to Connect
|
||||
* @return Function
|
||||
*/
|
||||
static pushStoreToConnect(): Function {
|
||||
return Store.pushToCloud
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the browser window
|
||||
*/
|
||||
static getWindow(): Electron.BrowserWindow {
|
||||
if (bw.win) {
|
||||
return bw.win
|
||||
} else {
|
||||
return BrowserWindow.getAllWindows()[0]
|
||||
}
|
||||
}
|
||||
|
||||
static loadPluginFrontend(path: string): void {
|
||||
|
||||
}
|
||||
|
||||
static loadJSFrontend(path: string): void {
|
||||
bw.win.webContents.executeJavaScript(fs.readFileSync(path, "utf8"));
|
||||
}
|
||||
static loadJSFrontend(path: string): void {
|
||||
bw.win.webContents.executeJavaScript(fs.readFileSync(path, "utf8"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,4 +64,4 @@
|
|||
"components/artist-chip",
|
||||
"components/hello-world",
|
||||
"components/inline-collection-list"
|
||||
]
|
||||
]
|
||||
|
|
|
@ -100,7 +100,8 @@
|
|||
"component": "<cider-groupings :data=\"browsepage\"></cider-groupings>",
|
||||
"condition": "page == 'groupings'",
|
||||
"onEnter": ""
|
||||
},{
|
||||
},
|
||||
{
|
||||
"page": "charts",
|
||||
"component": "<cider-charts :data=\"browsepage\"></cider-charts>",
|
||||
"condition": "page == 'charts'",
|
||||
|
@ -181,4 +182,4 @@
|
|||
"component": "<replay-page></replay-page>",
|
||||
"condition": "page == 'replay'"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
@ -4,366 +4,421 @@ import * as electron from "electron";
|
|||
const WebSocketServer = ws.Server;
|
||||
|
||||
interface standardResponse {
|
||||
status?: Number,
|
||||
message?: String,
|
||||
data?: any,
|
||||
type?: string,
|
||||
status?: Number;
|
||||
message?: String;
|
||||
data?: any;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
|
||||
export class wsapi {
|
||||
static clients: any;
|
||||
port: any = 26369
|
||||
wss: any = null
|
||||
clients: any = []
|
||||
private _win: any;
|
||||
static clients: any;
|
||||
port: any = 26369;
|
||||
wss: any = null;
|
||||
clients: any = [];
|
||||
private _win: any;
|
||||
|
||||
constructor(win: any) {
|
||||
this._win = win;
|
||||
}
|
||||
constructor(win: any) {
|
||||
this._win = win;
|
||||
}
|
||||
|
||||
createId() {
|
||||
// create random guid
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
||||
var r = (Math.random() * 16) | 0,
|
||||
v = c == "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
createId() {
|
||||
// create random guid
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = Math.random() * 16 | 0,
|
||||
v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
public async InitWebSockets() {
|
||||
electron.ipcMain.on("wsapi-updatePlaybackState", (_event: any, arg: any) => {
|
||||
this.updatePlaybackState(arg);
|
||||
});
|
||||
|
||||
public async InitWebSockets() {
|
||||
electron.ipcMain.on('wsapi-updatePlaybackState', (_event: any, arg: any) => {
|
||||
this.updatePlaybackState(arg);
|
||||
})
|
||||
electron.ipcMain.on("wsapi-returnQueue", (_event: any, arg: any) => {
|
||||
this.returnQueue(JSON.parse(arg));
|
||||
});
|
||||
|
||||
electron.ipcMain.on('wsapi-returnQueue', (_event: any, arg: any) => {
|
||||
this.returnQueue(JSON.parse(arg));
|
||||
});
|
||||
electron.ipcMain.on("wsapi-returnSearch", (_event: any, arg: any) => {
|
||||
console.log("SEARCH");
|
||||
this.returnSearch(JSON.parse(arg));
|
||||
});
|
||||
|
||||
electron.ipcMain.on('wsapi-returnSearch', (_event: any, arg: any) => {
|
||||
console.log("SEARCH")
|
||||
this.returnSearch(JSON.parse(arg));
|
||||
});
|
||||
electron.ipcMain.on("wsapi-returnSearchLibrary", (_event: any, arg: any) => {
|
||||
this.returnSearchLibrary(JSON.parse(arg));
|
||||
});
|
||||
|
||||
electron.ipcMain.on('wsapi-returnSearchLibrary', (_event: any, arg: any) => {
|
||||
this.returnSearchLibrary(JSON.parse(arg));
|
||||
});
|
||||
electron.ipcMain.on("wsapi-returnDynamic", (_event: any, arg: any, type: any) => {
|
||||
this.returnDynamic(JSON.parse(arg), type);
|
||||
});
|
||||
|
||||
electron.ipcMain.on('wsapi-returnDynamic', (_event: any, arg: any, type: any) => {
|
||||
this.returnDynamic(JSON.parse(arg), type);
|
||||
});
|
||||
electron.ipcMain.on("wsapi-returnMusicKitApi", (_event: any, arg: any, method: any) => {
|
||||
this.returnMusicKitApi(JSON.parse(arg), method);
|
||||
});
|
||||
|
||||
electron.ipcMain.on('wsapi-returnMusicKitApi', (_event: any, arg: any, method: any) => {
|
||||
this.returnMusicKitApi(JSON.parse(arg), method);
|
||||
});
|
||||
electron.ipcMain.on("wsapi-returnLyrics", (_event: any, arg: any) => {
|
||||
this.returnLyrics(JSON.parse(arg));
|
||||
});
|
||||
electron.ipcMain.on("wsapi-returnvolumeMax", (_event: any, arg: any) => {
|
||||
this.returnmaxVolume(JSON.parse(arg));
|
||||
});
|
||||
electron.ipcMain.on("wsapi-libraryStatus", (_event: any, inLibrary: boolean, rating: number) => {
|
||||
this.returnLibraryStatus(inLibrary, rating);
|
||||
});
|
||||
electron.ipcMain.on("wsapi-rate", (_event: any, kind: string, id: string, rating: number) => {
|
||||
this.returnRatingStatus(kind, id, rating);
|
||||
});
|
||||
electron.ipcMain.on("wsapi-change-library", (_event: any, kind: string, id: string, shouldAdd: boolean) => {
|
||||
this.returnLibraryChange(kind, id, shouldAdd);
|
||||
});
|
||||
this.wss = new WebSocketServer({
|
||||
port: this.port,
|
||||
perMessageDeflate: {
|
||||
zlibDeflateOptions: {
|
||||
// See zlib defaults.
|
||||
chunkSize: 1024,
|
||||
memLevel: 7,
|
||||
level: 3,
|
||||
},
|
||||
zlibInflateOptions: {
|
||||
chunkSize: 10 * 1024,
|
||||
},
|
||||
// Other options settable:
|
||||
clientNoContextTakeover: true, // Defaults to negotiated value.
|
||||
serverNoContextTakeover: true, // Defaults to negotiated value.
|
||||
serverMaxWindowBits: 10, // Defaults to negotiated value.
|
||||
// Below options specified as default values.
|
||||
concurrencyLimit: 10, // Limits zlib concurrency for perf.
|
||||
threshold: 1024, // Size (in bytes) below which messages
|
||||
// should not be compressed if context takeover is disabled.
|
||||
},
|
||||
});
|
||||
console.log(`WebSocketServer started on port: ${this.port}`);
|
||||
|
||||
electron.ipcMain.on('wsapi-returnLyrics', (_event: any, arg: any) => {
|
||||
this.returnLyrics(JSON.parse(arg));
|
||||
});
|
||||
electron.ipcMain.on('wsapi-returnvolumeMax', (_event: any, arg: any) => {
|
||||
this.returnmaxVolume(JSON.parse(arg));
|
||||
});
|
||||
electron.ipcMain.on('wsapi-libraryStatus', (_event: any, inLibrary: boolean, rating: number) => {
|
||||
this.returnLibraryStatus(inLibrary, rating);
|
||||
});
|
||||
electron.ipcMain.on('wsapi-rate', (_event: any, kind: string, id: string, rating: number) => {
|
||||
this.returnRatingStatus(kind, id, rating);
|
||||
});
|
||||
electron.ipcMain.on('wsapi-change-library', (_event: any, kind: string, id: string, shouldAdd: boolean) => {
|
||||
this.returnLibraryChange(kind, id, shouldAdd);
|
||||
});
|
||||
this.wss = new WebSocketServer({
|
||||
port: this.port,
|
||||
perMessageDeflate: {
|
||||
zlibDeflateOptions: {
|
||||
// See zlib defaults.
|
||||
chunkSize: 1024,
|
||||
memLevel: 7,
|
||||
level: 3
|
||||
},
|
||||
zlibInflateOptions: {
|
||||
chunkSize: 10 * 1024
|
||||
},
|
||||
// Other options settable:
|
||||
clientNoContextTakeover: true, // Defaults to negotiated value.
|
||||
serverNoContextTakeover: true, // Defaults to negotiated value.
|
||||
serverMaxWindowBits: 10, // Defaults to negotiated value.
|
||||
// Below options specified as default values.
|
||||
concurrencyLimit: 10, // Limits zlib concurrency for perf.
|
||||
threshold: 1024 // Size (in bytes) below which messages
|
||||
// should not be compressed if context takeover is disabled.
|
||||
const defaultResponse: standardResponse = {
|
||||
status: 0,
|
||||
data: {},
|
||||
message: "OK",
|
||||
type: "generic",
|
||||
};
|
||||
|
||||
this.wss.on("connection", (ws: any) => {
|
||||
ws.id = this.createId();
|
||||
console.log(`Client ${ws.id} connected`);
|
||||
this.clients.push(ws);
|
||||
ws.on("message", function incoming(_message: any) {});
|
||||
// ws on message
|
||||
ws.on("message", (message: any) => {
|
||||
let data = JSON.parse(message);
|
||||
let response: standardResponse = {
|
||||
status: 0,
|
||||
data: {},
|
||||
message: "OK",
|
||||
type: "generic",
|
||||
};
|
||||
if (data.action) {
|
||||
data.action.toLowerCase();
|
||||
}
|
||||
switch (data.action) {
|
||||
default:
|
||||
response.message = "Action not found";
|
||||
break;
|
||||
case "identify":
|
||||
response.message = "Thanks for identifying!";
|
||||
response.data = {
|
||||
id: ws.id,
|
||||
};
|
||||
ws.identity = {
|
||||
name: data.name,
|
||||
author: data.author,
|
||||
description: data.description,
|
||||
version: data.version,
|
||||
};
|
||||
break;
|
||||
case "play-next":
|
||||
this._win.webContents.executeJavaScript(`wsapi.playNext(\`${data.type}\`,\`${data.id}\`)`);
|
||||
response.message = "Play Next";
|
||||
break;
|
||||
case "play-later":
|
||||
this._win.webContents.executeJavaScript(`wsapi.playLater(\`${data.type}\`,\`${data.id}\`)`);
|
||||
response.message = "Play Later";
|
||||
break;
|
||||
case "quick-play":
|
||||
this._win.webContents.executeJavaScript(`wsapi.quickPlay(\`${data.term}\`)`);
|
||||
response.message = "Quick Play";
|
||||
break;
|
||||
case "get-lyrics":
|
||||
this._win.webContents.executeJavaScript(`wsapi.getLyrics()`);
|
||||
break;
|
||||
case "shuffle":
|
||||
this._win.webContents.executeJavaScript(`wsapi.toggleShuffle()`);
|
||||
break;
|
||||
case "set-shuffle":
|
||||
if (data.shuffle == true) {
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 1`);
|
||||
} else {
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 0`);
|
||||
}
|
||||
})
|
||||
console.log(`WebSocketServer started on port: ${this.port}`);
|
||||
|
||||
const defaultResponse: standardResponse = {status: 0, data: {}, message: "OK", type: "generic"};
|
||||
|
||||
|
||||
this.wss.on('connection', (ws: any) => {
|
||||
ws.id = this.createId();
|
||||
console.log(`Client ${ws.id} connected`)
|
||||
this.clients.push(ws);
|
||||
ws.on('message', function incoming(_message: any) {
|
||||
|
||||
});
|
||||
// ws on message
|
||||
ws.on('message', (message: any) => {
|
||||
let data = JSON.parse(message);
|
||||
let response: standardResponse = {status: 0, data: {}, message: "OK", type: "generic"};
|
||||
if (data.action) {
|
||||
data.action.toLowerCase();
|
||||
}
|
||||
switch (data.action) {
|
||||
default:
|
||||
response.message = "Action not found";
|
||||
break;
|
||||
case "identify":
|
||||
response.message = "Thanks for identifying!"
|
||||
response.data = {
|
||||
id: ws.id
|
||||
}
|
||||
ws.identity = {
|
||||
name: data.name,
|
||||
author: data.author,
|
||||
description: data.description,
|
||||
version: data.version
|
||||
}
|
||||
break;
|
||||
case "play-next":
|
||||
this._win.webContents.executeJavaScript(`wsapi.playNext(\`${data.type}\`,\`${data.id}\`)`);
|
||||
response.message = "Play Next";
|
||||
break;
|
||||
case "play-later":
|
||||
this._win.webContents.executeJavaScript(`wsapi.playLater(\`${data.type}\`,\`${data.id}\`)`);
|
||||
response.message = "Play Later";
|
||||
break;
|
||||
case "quick-play":
|
||||
this._win.webContents.executeJavaScript(`wsapi.quickPlay(\`${data.term}\`)`);
|
||||
response.message = "Quick Play";
|
||||
break;
|
||||
case "get-lyrics":
|
||||
this._win.webContents.executeJavaScript(`wsapi.getLyrics()`);
|
||||
break;
|
||||
case "shuffle":
|
||||
this._win.webContents.executeJavaScript(`wsapi.toggleShuffle()`);
|
||||
break;
|
||||
case "set-shuffle":
|
||||
if (data.shuffle == true) {
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 1`);
|
||||
} else {
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 0`);
|
||||
}
|
||||
break;
|
||||
case "repeat":
|
||||
this._win.webContents.executeJavaScript(`wsapi.toggleRepeat()`);
|
||||
break;
|
||||
case "seek":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${parseFloat(data.time)})`);
|
||||
response.message = "Seek";
|
||||
break;
|
||||
case "pause":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().pause()`);
|
||||
response.message = "Paused";
|
||||
break;
|
||||
case "playpause":
|
||||
this._win.webContents.executeJavaScript(`MusicKitInterop.playPause()`);
|
||||
response.message = "Play/Pause";
|
||||
break
|
||||
case "play":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().play()`);
|
||||
response.message = "Playing";
|
||||
break;
|
||||
case "stop":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().stop()`);
|
||||
response.message = "Stopped";
|
||||
break;
|
||||
case "volumeMax":
|
||||
this._win.webContents.executeJavaScript(`wsapi.getmaxVolume()`);
|
||||
response.message = "maxVolume";
|
||||
break;
|
||||
case "volume":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().volume = ${parseFloat(data.volume)}`);
|
||||
response.message = "Volume";
|
||||
break;
|
||||
case "mute":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().mute()`);
|
||||
response.message = "Muted";
|
||||
break;
|
||||
case "unmute":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().unmute()`);
|
||||
response.message = "Unmuted";
|
||||
break;
|
||||
case "next":
|
||||
this._win.webContents.executeJavaScript(`if (MusicKit.getInstance().queue.nextPlayableItemIndex != -1 && MusicKit.getInstance().queue.nextPlayableItemIndex != null) {
|
||||
break;
|
||||
case "repeat":
|
||||
this._win.webContents.executeJavaScript(`wsapi.toggleRepeat()`);
|
||||
break;
|
||||
case "seek":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${parseFloat(data.time)})`);
|
||||
response.message = "Seek";
|
||||
break;
|
||||
case "pause":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().pause()`);
|
||||
response.message = "Paused";
|
||||
break;
|
||||
case "playpause":
|
||||
this._win.webContents.executeJavaScript(`MusicKitInterop.playPause()`);
|
||||
response.message = "Play/Pause";
|
||||
break;
|
||||
case "play":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().play()`);
|
||||
response.message = "Playing";
|
||||
break;
|
||||
case "stop":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().stop()`);
|
||||
response.message = "Stopped";
|
||||
break;
|
||||
case "volumeMax":
|
||||
this._win.webContents.executeJavaScript(`wsapi.getmaxVolume()`);
|
||||
response.message = "maxVolume";
|
||||
break;
|
||||
case "volume":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().volume = ${parseFloat(data.volume)}`);
|
||||
response.message = "Volume";
|
||||
break;
|
||||
case "mute":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().mute()`);
|
||||
response.message = "Muted";
|
||||
break;
|
||||
case "unmute":
|
||||
this._win.webContents.executeJavaScript(`MusicKit.getInstance().unmute()`);
|
||||
response.message = "Unmuted";
|
||||
break;
|
||||
case "next":
|
||||
this._win.webContents.executeJavaScript(`if (MusicKit.getInstance().queue.nextPlayableItemIndex != -1 && MusicKit.getInstance().queue.nextPlayableItemIndex != null) {
|
||||
try {
|
||||
app.prevButtonBackIndicator = false;
|
||||
} catch (e) { }
|
||||
MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.nextPlayableItemIndex);}`);
|
||||
response.message = "Next";
|
||||
break;
|
||||
case "previous":
|
||||
this._win.webContents.executeJavaScript(`if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null) {MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex)}`);
|
||||
response.message = "Previous";
|
||||
break;
|
||||
case "musickit-api":
|
||||
this._win.webContents.executeJavaScript(`wsapi.musickitApi(\`${data.method}\`, \`${data.id}\`, ${JSON.stringify(data.params)} , ${data.library})`);
|
||||
break;
|
||||
case "musickit-library-api":
|
||||
break;
|
||||
case "set-autoplay":
|
||||
this._win.webContents.executeJavaScript(`wsapi.setAutoplay(${data.autoplay})`);
|
||||
break;
|
||||
case "queue-move":
|
||||
this._win.webContents.executeJavaScript(`wsapi.moveQueueItem(${data.from},${data.to})`);
|
||||
break;
|
||||
case "get-queue":
|
||||
this._win.webContents.executeJavaScript(`wsapi.getQueue()`);
|
||||
break;
|
||||
case "search":
|
||||
if (!data.limit) {
|
||||
data.limit = 10;
|
||||
}
|
||||
this._win.webContents.executeJavaScript(`wsapi.search(\`${data.term}\`, \`${data.limit}\`)`);
|
||||
break;
|
||||
case "library-search":
|
||||
if (!data.limit) {
|
||||
data.limit = 10;
|
||||
}
|
||||
this._win.webContents.executeJavaScript(`wsapi.searchLibrary(\`${data.term}\`, \`${data.limit}\`)`);
|
||||
break;
|
||||
case "show-window":
|
||||
this._win.show()
|
||||
break;
|
||||
case "hide-window":
|
||||
this._win.hide()
|
||||
break;
|
||||
case "play-mediaitem":
|
||||
this._win.webContents.executeJavaScript(`wsapi.playTrackById("${data.id}", \`${data.kind}\`)`);
|
||||
response.message = "Playing track";
|
||||
break;
|
||||
case "get-status":
|
||||
response.data = {
|
||||
isAuthorized: true
|
||||
};
|
||||
response.message = "Status";
|
||||
break;
|
||||
case "get-currentmediaitem":
|
||||
this._win.webContents.executeJavaScript(`wsapi.getPlaybackState()`);
|
||||
break;
|
||||
case "library-status":
|
||||
this._win.webContents.executeJavaScript(`wsapi.getLibraryStatus("${data.type}", "${data.id}")`);
|
||||
break;
|
||||
case "rating":
|
||||
this._win.webContents.executeJavaScript(`wsapi.rate("${data.type}", "${data.id}", ${data.rating})`);
|
||||
break;
|
||||
case "change-library":
|
||||
this._win.webContents.executeJavaScript(`wsapi.changeLibrary("${data.type}", "${data.id}", ${data.add})`);
|
||||
break;
|
||||
case "quit":
|
||||
electron.app.quit();
|
||||
break;
|
||||
}
|
||||
ws.send(JSON.stringify(response));
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
// remove client from list
|
||||
this.clients.splice(wsapi.clients.indexOf(ws), 1);
|
||||
console.log(`Client ${ws.id} disconnected`);
|
||||
});
|
||||
ws.send(JSON.stringify(defaultResponse));
|
||||
});
|
||||
}
|
||||
|
||||
sendToClient(_id: any) {
|
||||
// replace the clients.forEach with a filter to find the client that requested
|
||||
}
|
||||
|
||||
updatePlaybackState(attr: any) {
|
||||
const response: standardResponse = {status: 0, data: attr, message: "OK", type: "playbackStateUpdate"};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnMusicKitApi(results: any, method: any) {
|
||||
const response: standardResponse = {status: 0, data: results, message: "OK", type: `musickitapi.${method}`};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnDynamic(results: any, type: any) {
|
||||
const response: standardResponse = {status: 0, data: results, message: "OK", type: type};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnLyrics(results: any) {
|
||||
const response: standardResponse = {status: 0, data: results, message: "OK", type: "lyrics"};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnSearch(results: any) {
|
||||
const response: standardResponse = {status: 0, data: results, message: "OK", type: "searchResults"};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnSearchLibrary(results: any) {
|
||||
const response: standardResponse = {status: 0, data: results, message: "OK", type: "searchResultsLibrary"};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnQueue(queue: any) {
|
||||
const response: standardResponse = {status: 0, data: queue, message: "OK", type: "queue"};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnmaxVolume(vol: any) {
|
||||
const response: standardResponse = {status: 0, data: vol, message: "OK", type: "maxVolume"};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnLibraryStatus(inLibrary: boolean, rating: number) {
|
||||
const response: standardResponse = {
|
||||
status: 0, data: {
|
||||
inLibrary, rating
|
||||
}, message: "OK", type: "libraryStatus"
|
||||
response.message = "Next";
|
||||
break;
|
||||
case "previous":
|
||||
this._win.webContents.executeJavaScript(
|
||||
`if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null) {MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex)}`
|
||||
);
|
||||
response.message = "Previous";
|
||||
break;
|
||||
case "musickit-api":
|
||||
this._win.webContents.executeJavaScript(`wsapi.musickitApi(\`${data.method}\`, \`${data.id}\`, ${JSON.stringify(data.params)} , ${data.library})`);
|
||||
break;
|
||||
case "musickit-library-api":
|
||||
break;
|
||||
case "set-autoplay":
|
||||
this._win.webContents.executeJavaScript(`wsapi.setAutoplay(${data.autoplay})`);
|
||||
break;
|
||||
case "queue-move":
|
||||
this._win.webContents.executeJavaScript(`wsapi.moveQueueItem(${data.from},${data.to})`);
|
||||
break;
|
||||
case "get-queue":
|
||||
this._win.webContents.executeJavaScript(`wsapi.getQueue()`);
|
||||
break;
|
||||
case "search":
|
||||
if (!data.limit) {
|
||||
data.limit = 10;
|
||||
}
|
||||
this._win.webContents.executeJavaScript(`wsapi.search(\`${data.term}\`, \`${data.limit}\`)`);
|
||||
break;
|
||||
case "library-search":
|
||||
if (!data.limit) {
|
||||
data.limit = 10;
|
||||
}
|
||||
this._win.webContents.executeJavaScript(`wsapi.searchLibrary(\`${data.term}\`, \`${data.limit}\`)`);
|
||||
break;
|
||||
case "show-window":
|
||||
this._win.show();
|
||||
break;
|
||||
case "hide-window":
|
||||
this._win.hide();
|
||||
break;
|
||||
case "play-mediaitem":
|
||||
this._win.webContents.executeJavaScript(`wsapi.playTrackById("${data.id}", \`${data.kind}\`)`);
|
||||
response.message = "Playing track";
|
||||
break;
|
||||
case "get-status":
|
||||
response.data = {
|
||||
isAuthorized: true,
|
||||
};
|
||||
response.message = "Status";
|
||||
break;
|
||||
case "get-currentmediaitem":
|
||||
this._win.webContents.executeJavaScript(`wsapi.getPlaybackState()`);
|
||||
break;
|
||||
case "library-status":
|
||||
this._win.webContents.executeJavaScript(`wsapi.getLibraryStatus("${data.type}", "${data.id}")`);
|
||||
break;
|
||||
case "rating":
|
||||
this._win.webContents.executeJavaScript(`wsapi.rate("${data.type}", "${data.id}", ${data.rating})`);
|
||||
break;
|
||||
case "change-library":
|
||||
this._win.webContents.executeJavaScript(`wsapi.changeLibrary("${data.type}", "${data.id}", ${data.add})`);
|
||||
break;
|
||||
case "quit":
|
||||
electron.app.quit();
|
||||
break;
|
||||
}
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
ws.send(JSON.stringify(response));
|
||||
});
|
||||
|
||||
returnRatingStatus(kind: string, id: string, rating: number) {
|
||||
const response: standardResponse = {
|
||||
status: 0, data: { kind, id, rating },
|
||||
message: "OK", type: "rate"
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
ws.on("close", () => {
|
||||
// remove client from list
|
||||
this.clients.splice(wsapi.clients.indexOf(ws), 1);
|
||||
console.log(`Client ${ws.id} disconnected`);
|
||||
});
|
||||
ws.send(JSON.stringify(defaultResponse));
|
||||
});
|
||||
}
|
||||
|
||||
returnLibraryChange(kind: string, id: string, shouldAdd: boolean) {
|
||||
const response: standardResponse = {
|
||||
status: 0, data: { kind, id, add: shouldAdd },
|
||||
message: "OK", type: "change-library"
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
}
|
||||
sendToClient(_id: any) {
|
||||
// replace the clients.forEach with a filter to find the client that requested
|
||||
}
|
||||
|
||||
updatePlaybackState(attr: any) {
|
||||
const response: standardResponse = {
|
||||
status: 0,
|
||||
data: attr,
|
||||
message: "OK",
|
||||
type: "playbackStateUpdate",
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnMusicKitApi(results: any, method: any) {
|
||||
const response: standardResponse = {
|
||||
status: 0,
|
||||
data: results,
|
||||
message: "OK",
|
||||
type: `musickitapi.${method}`,
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnDynamic(results: any, type: any) {
|
||||
const response: standardResponse = {
|
||||
status: 0,
|
||||
data: results,
|
||||
message: "OK",
|
||||
type: type,
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnLyrics(results: any) {
|
||||
const response: standardResponse = {
|
||||
status: 0,
|
||||
data: results,
|
||||
message: "OK",
|
||||
type: "lyrics",
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnSearch(results: any) {
|
||||
const response: standardResponse = {
|
||||
status: 0,
|
||||
data: results,
|
||||
message: "OK",
|
||||
type: "searchResults",
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnSearchLibrary(results: any) {
|
||||
const response: standardResponse = {
|
||||
status: 0,
|
||||
data: results,
|
||||
message: "OK",
|
||||
type: "searchResultsLibrary",
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnQueue(queue: any) {
|
||||
const response: standardResponse = {
|
||||
status: 0,
|
||||
data: queue,
|
||||
message: "OK",
|
||||
type: "queue",
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnmaxVolume(vol: any) {
|
||||
const response: standardResponse = {
|
||||
status: 0,
|
||||
data: vol,
|
||||
message: "OK",
|
||||
type: "maxVolume",
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnLibraryStatus(inLibrary: boolean, rating: number) {
|
||||
const response: standardResponse = {
|
||||
status: 0,
|
||||
data: {
|
||||
inLibrary,
|
||||
rating,
|
||||
},
|
||||
message: "OK",
|
||||
type: "libraryStatus",
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnRatingStatus(kind: string, id: string, rating: number) {
|
||||
const response: standardResponse = {
|
||||
status: 0,
|
||||
data: { kind, id, rating },
|
||||
message: "OK",
|
||||
type: "rate",
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
|
||||
returnLibraryChange(kind: string, id: string, shouldAdd: boolean) {
|
||||
const response: standardResponse = {
|
||||
status: 0,
|
||||
data: { kind, id, add: shouldAdd },
|
||||
message: "OK",
|
||||
type: "change-library",
|
||||
};
|
||||
this.clients.forEach(function each(client: any) {
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
require("v8-compile-cache");
|
||||
|
||||
import {join} from "path";
|
||||
import {app} from "electron"
|
||||
import { join } from "path";
|
||||
import { app } from "electron";
|
||||
if (!app.isPackaged) {
|
||||
app.setPath("userData", join(app.getPath("appData"), "Cider"));
|
||||
app.setPath("userData", join(app.getPath("appData"), "Cider"));
|
||||
}
|
||||
|
||||
import {Store} from "./base/store";
|
||||
import {AppEvents} from "./base/app";
|
||||
import {Plugins} from "./base/plugins";
|
||||
import {BrowserWindow} from "./base/browserwindow";
|
||||
import {init as Sentry} from "@sentry/electron";
|
||||
import {RewriteFrames} from "@sentry/integrations";
|
||||
import {components, ipcMain} from "electron"
|
||||
import { Store } from "./base/store";
|
||||
import { AppEvents } from "./base/app";
|
||||
import { Plugins } from "./base/plugins";
|
||||
import { BrowserWindow } from "./base/browserwindow";
|
||||
import { init as Sentry } from "@sentry/electron";
|
||||
import { RewriteFrames } from "@sentry/integrations";
|
||||
import { components, ipcMain } from "electron";
|
||||
|
||||
// Analytics for debugging fun yeah.
|
||||
Sentry({
|
||||
dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214",
|
||||
integrations: [
|
||||
new RewriteFrames({
|
||||
root: process.cwd(),
|
||||
}),
|
||||
],
|
||||
dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214",
|
||||
integrations: [
|
||||
new RewriteFrames({
|
||||
root: process.cwd(),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
new Store();
|
||||
|
@ -33,31 +33,30 @@ const CiderPlug = new Plugins();
|
|||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
app.on("ready", () => {
|
||||
Cider.ready(CiderPlug);
|
||||
Cider.ready(CiderPlug);
|
||||
|
||||
console.log("[Cider] Application is Ready. Creating Window.")
|
||||
if (!app.isPackaged) {
|
||||
console.info("[Cider] Running in development mode.")
|
||||
require("vue-devtools").install()
|
||||
}
|
||||
console.log("[Cider] Application is Ready. Creating Window.");
|
||||
if (!app.isPackaged) {
|
||||
console.info("[Cider] Running in development mode.");
|
||||
require("vue-devtools").install();
|
||||
}
|
||||
|
||||
components.whenReady().then(async () => {
|
||||
const bw = new BrowserWindow()
|
||||
const win = await bw.createWindow()
|
||||
components.whenReady().then(async () => {
|
||||
const bw = new BrowserWindow();
|
||||
const win = await bw.createWindow();
|
||||
|
||||
app.getGPUInfo("complete").then(gpuInfo => {
|
||||
console.log(gpuInfo)
|
||||
})
|
||||
|
||||
console.log("[Cider][Widevine] Status:", components.status());
|
||||
Cider.bwCreated();
|
||||
win.on("ready-to-show", () => {
|
||||
console.debug("[Cider] Window is Ready.")
|
||||
CiderPlug.callPlugins("onReady", win);
|
||||
win.show();
|
||||
});
|
||||
app.getGPUInfo("complete").then((gpuInfo) => {
|
||||
console.log(gpuInfo);
|
||||
});
|
||||
|
||||
console.log("[Cider][Widevine] Status:", components.status());
|
||||
Cider.bwCreated();
|
||||
win.on("ready-to-show", () => {
|
||||
console.debug("[Cider] Window is Ready.");
|
||||
CiderPlug.callPlugins("onReady", win);
|
||||
win.show();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -65,20 +64,20 @@ app.on("ready", () => {
|
|||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
ipcMain.handle("renderer-ready", (event) => {
|
||||
CiderPlug.callPlugins("onRendererReady", event);
|
||||
})
|
||||
CiderPlug.callPlugins("onRendererReady", event);
|
||||
});
|
||||
|
||||
ipcMain.on("playbackStateDidChange", (_event, attributes) => {
|
||||
CiderPlug.callPlugins("onPlaybackStateDidChange", attributes);
|
||||
CiderPlug.callPlugins("onPlaybackStateDidChange", attributes);
|
||||
});
|
||||
|
||||
ipcMain.on("nowPlayingItemDidChange", (_event, attributes) => {
|
||||
CiderPlug.callPlugins("onNowPlayingItemDidChange", attributes);
|
||||
CiderPlug.callPlugins("onNowPlayingItemDidChange", attributes);
|
||||
});
|
||||
|
||||
app.on("before-quit", () => {
|
||||
CiderPlug.callPlugins("onBeforeQuit");
|
||||
console.warn(`${app.getName()} exited.`);
|
||||
CiderPlug.callPlugins("onBeforeQuit");
|
||||
console.warn(`${app.getName()} exited.`);
|
||||
});
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -87,20 +86,20 @@ app.on("before-quit", () => {
|
|||
|
||||
// @ts-ignore
|
||||
app.on("widevine-ready", (version, lastVersion) => {
|
||||
if (null !== lastVersion) {
|
||||
console.log("[Cider][Widevine] Widevine " + version + ", upgraded from " + lastVersion + ", is ready to be used!")
|
||||
} else {
|
||||
console.log("[Cider][Widevine] Widevine " + version + " is ready to be used!")
|
||||
}
|
||||
})
|
||||
if (null !== lastVersion) {
|
||||
console.log("[Cider][Widevine] Widevine " + version + ", upgraded from " + lastVersion + ", is ready to be used!");
|
||||
} else {
|
||||
console.log("[Cider][Widevine] Widevine " + version + " is ready to be used!");
|
||||
}
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
app.on("widevine-update-pending", (currentVersion, pendingVersion) => {
|
||||
console.log("[Cider][Widevine] Widevine " + currentVersion + " is ready to be upgraded to " + pendingVersion + "!")
|
||||
})
|
||||
console.log("[Cider][Widevine] Widevine " + currentVersion + " is ready to be upgraded to " + pendingVersion + "!");
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
app.on("widevine-error", (error) => {
|
||||
console.log("[Cider][Widevine] Widevine installation encountered an error: " + error)
|
||||
app.exit()
|
||||
})
|
||||
console.log("[Cider][Widevine] Widevine installation encountered an error: " + error);
|
||||
app.exit();
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import * as PouchDB from 'pouchdb-node';
|
||||
import {join} from 'path';
|
||||
import {app} from "electron";
|
||||
PouchDB.plugin(require('pouchdb-upsert'));
|
||||
import * as PouchDB from "pouchdb-node";
|
||||
import { join } from "path";
|
||||
import { app } from "electron";
|
||||
PouchDB.plugin(require("pouchdb-upsert"));
|
||||
export class ProviderDB {
|
||||
public static db: any = null
|
||||
static init() {
|
||||
if (ProviderDB.db == null){
|
||||
ProviderDB.db = new PouchDB(join(app.getPath('userData'), 'tracksdb'))
|
||||
}
|
||||
public static db: any = null;
|
||||
static init() {
|
||||
if (ProviderDB.db == null) {
|
||||
ProviderDB.db = new PouchDB(join(app.getPath("userData"), "tracksdb"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,180 +1,197 @@
|
|||
import { ProviderDB } from "./db";
|
||||
import * as path from 'path';
|
||||
const { readdir } = require('fs').promises;
|
||||
import { utils } from '../../base/utils';
|
||||
import * as mm from 'music-metadata';
|
||||
import {Md5} from 'ts-md5/dist/md5';
|
||||
import * as path from "path";
|
||||
const { readdir } = require("fs").promises;
|
||||
import { utils } from "../../base/utils";
|
||||
import * as mm from "music-metadata";
|
||||
import { Md5 } from "ts-md5/dist/md5";
|
||||
import e from "express";
|
||||
import { EventEmitter } from 'events';
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
export class LocalFiles {
|
||||
static localSongs: any = [];
|
||||
static localSongsArts: any = [];
|
||||
public static DB = ProviderDB.db;
|
||||
static eventEmitter = new EventEmitter();
|
||||
static localSongs: any = [];
|
||||
static localSongsArts: any = [];
|
||||
public static DB = ProviderDB.db;
|
||||
static eventEmitter = new EventEmitter();
|
||||
|
||||
static getDataType(item_id : String | any){
|
||||
if ((item_id ?? ('')).startsWith('ciderlocalart'))
|
||||
return 'artwork'
|
||||
else if ((item_id ?? ('')).startsWith('ciderlocal'))
|
||||
return 'track'
|
||||
static getDataType(item_id: String | any) {
|
||||
if ((item_id ?? "").startsWith("ciderlocalart")) return "artwork";
|
||||
else if ((item_id ?? "").startsWith("ciderlocal")) return "track";
|
||||
}
|
||||
|
||||
static async sendOldLibrary() {
|
||||
ProviderDB.init();
|
||||
let rows = (await ProviderDB.db.allDocs({ include_docs: true, attachments: true })).rows.map((item: any) => {
|
||||
return item.doc;
|
||||
});
|
||||
let tracks = rows.filter((item: any) => {
|
||||
return this.getDataType(item._id) == "track";
|
||||
});
|
||||
let arts = rows.filter((item: any) => {
|
||||
return this.getDataType(item._id) == "artwork";
|
||||
});
|
||||
this.localSongs = tracks;
|
||||
this.localSongsArts = arts;
|
||||
return tracks;
|
||||
}
|
||||
|
||||
static async scanLibrary() {
|
||||
ProviderDB.init();
|
||||
let folders = utils.getStoreValue("libraryPrefs.localPaths");
|
||||
if (folders == null || folders.length == null || folders.length == 0) folders = [];
|
||||
let files: any[] = [];
|
||||
for (var folder of folders) {
|
||||
// get files from the Music folder
|
||||
files = files.concat(await LocalFiles.getFiles(folder));
|
||||
}
|
||||
|
||||
static async sendOldLibrary() {
|
||||
ProviderDB.init()
|
||||
let rows = (await ProviderDB.db.allDocs({include_docs: true,
|
||||
attachments: true})).rows.map((item: any)=>{return item.doc})
|
||||
let tracks = rows.filter((item: any) => {return this.getDataType(item._id) == "track"})
|
||||
let arts = rows.filter((item: any) => {return this.getDataType(item._id) == "artwork"})
|
||||
this.localSongs = tracks;
|
||||
this.localSongsArts = arts;
|
||||
return tracks;
|
||||
}
|
||||
|
||||
static async scanLibrary() {
|
||||
ProviderDB.init()
|
||||
let folders = utils.getStoreValue("libraryPrefs.localPaths")
|
||||
if (folders == null || folders.length == null || folders.length == 0) folders = []
|
||||
let files: any[] = []
|
||||
for (var folder of folders) {
|
||||
// get files from the Music folder
|
||||
files = files.concat(await LocalFiles.getFiles(folder))
|
||||
let supporttedformats = ["mp3", "aac", "webm", "flac", "m4a", "ogg", "wav", "opus"];
|
||||
let audiofiles = files.filter((f) => supporttedformats.includes(f.substring(f.lastIndexOf(".") + 1)));
|
||||
let metadatalist = [];
|
||||
let metadatalistart = [];
|
||||
let numid = 0;
|
||||
for (var audio of audiofiles) {
|
||||
try {
|
||||
const metadata = await mm.parseFile(audio);
|
||||
let lochash = Md5.hashStr(audio) ?? numid;
|
||||
if (metadata != null) {
|
||||
let form = {
|
||||
id: "ciderlocal" + lochash,
|
||||
_id: "ciderlocal" + lochash,
|
||||
type: "podcast-episodes",
|
||||
href: audio,
|
||||
attributes: {
|
||||
artwork: {
|
||||
width: 3000,
|
||||
height: 3000,
|
||||
url: "/ciderlocalart/" + "ciderlocal" + lochash,
|
||||
},
|
||||
topics: [],
|
||||
url: "",
|
||||
subscribable: true,
|
||||
mediaKind: "audio",
|
||||
genreNames: [""],
|
||||
// "playParams": {
|
||||
// "id": "ciderlocal" + numid,
|
||||
// "kind": "podcast",
|
||||
// "isLibrary": true,
|
||||
// "reporting": false },
|
||||
trackNumber: metadata.common.track?.no ?? 0,
|
||||
discNumber: metadata.common.disk?.no ?? 0,
|
||||
name: metadata.common.title ?? audio.substring(audio.lastIndexOf("\\") + 1),
|
||||
albumName: metadata.common.album,
|
||||
artistName: metadata.common.artist,
|
||||
copyright: metadata.common.copyright ?? "",
|
||||
assetUrl: "file:///" + audio,
|
||||
contentAdvisory: "",
|
||||
releaseDateTime: `${metadata?.common?.year ?? "2022"}-05-13T00:23:00Z`,
|
||||
durationInMillis: Math.floor((metadata.format.duration ?? 0) * 1000),
|
||||
bitrate: Math.floor((metadata.format?.bitrate ?? 0) / 1000),
|
||||
offers: [
|
||||
{
|
||||
kind: "get",
|
||||
type: "STDQ",
|
||||
},
|
||||
],
|
||||
contentRating: "clean",
|
||||
},
|
||||
flavor: Math.floor((metadata.format?.bitrate ?? 0) / 1000),
|
||||
localFilesMetadata: {
|
||||
lossless: metadata.format?.lossless,
|
||||
container: metadata.format?.container,
|
||||
bitDepth: metadata.format?.bitsPerSample ?? 0,
|
||||
sampleRate: metadata.format?.sampleRate ?? 0,
|
||||
},
|
||||
};
|
||||
let art = {
|
||||
id: "ciderlocal" + lochash,
|
||||
_id: "ciderlocalart" + lochash,
|
||||
url: metadata.common.picture != undefined ? metadata.common.picture[0].data.toString("base64") : "",
|
||||
};
|
||||
metadatalistart.push(art);
|
||||
numid += 1;
|
||||
ProviderDB.db.putIfNotExists(form);
|
||||
ProviderDB.db.putIfNotExists(art);
|
||||
metadatalist.push(form);
|
||||
|
||||
if (this.localSongs.length === 0 && numid % 10 === 0) {
|
||||
// send updated chunks only if there is no previous database
|
||||
this.eventEmitter.emit("newtracks", metadatalist);
|
||||
}
|
||||
}
|
||||
|
||||
let supporttedformats = ["mp3", "aac", "webm", "flac", "m4a", "ogg", "wav", "opus"]
|
||||
let audiofiles = files.filter(f => supporttedformats.includes(f.substring(f.lastIndexOf('.') + 1)));
|
||||
let metadatalist = []
|
||||
let metadatalistart = []
|
||||
let numid = 0;
|
||||
for (var audio of audiofiles) {
|
||||
try {
|
||||
const metadata = await mm.parseFile(audio);
|
||||
let lochash = Md5.hashStr(audio) ?? numid;
|
||||
if (metadata != null) {
|
||||
let form = {
|
||||
"id": "ciderlocal" + lochash,
|
||||
"_id": "ciderlocal" + lochash,
|
||||
"type": "podcast-episodes",
|
||||
"href": audio,
|
||||
"attributes": {
|
||||
"artwork": {
|
||||
"width": 3000,
|
||||
"height": 3000,
|
||||
"url": "/ciderlocalart/" + "ciderlocal" + lochash,
|
||||
},
|
||||
"topics": [],
|
||||
"url": "",
|
||||
"subscribable": true,
|
||||
"mediaKind": "audio",
|
||||
"genreNames": [
|
||||
""
|
||||
],
|
||||
// "playParams": {
|
||||
// "id": "ciderlocal" + numid,
|
||||
// "kind": "podcast",
|
||||
// "isLibrary": true,
|
||||
// "reporting": false },
|
||||
"trackNumber": metadata.common.track?.no ?? 0,
|
||||
"discNumber": metadata.common.disk?.no ?? 0,
|
||||
"name": metadata.common.title ?? audio.substring(audio.lastIndexOf('\\') + 1),
|
||||
"albumName": metadata.common.album,
|
||||
"artistName": metadata.common.artist,
|
||||
"copyright": metadata.common.copyright ?? "",
|
||||
"assetUrl": "file:///" + audio,
|
||||
"contentAdvisory": "",
|
||||
"releaseDateTime": `${metadata?.common?.year ?? '2022'}-05-13T00:23:00Z`,
|
||||
"durationInMillis": Math.floor((metadata.format.duration ?? 0) * 1000),
|
||||
"bitrate": Math.floor((metadata.format?.bitrate ?? 0) / 1000),
|
||||
"offers": [
|
||||
{
|
||||
"kind": "get",
|
||||
"type": "STDQ"
|
||||
}
|
||||
],
|
||||
"contentRating": "clean"
|
||||
},
|
||||
flavor: Math.floor((metadata.format?.bitrate ?? 0) / 1000),
|
||||
localFilesMetadata: {
|
||||
lossless: metadata.format?.lossless,
|
||||
container: metadata.format?.container,
|
||||
bitDepth: metadata.format?.bitsPerSample ?? 0,
|
||||
sampleRate: metadata.format?.sampleRate ?? 0,
|
||||
},
|
||||
};
|
||||
let art = {
|
||||
id: "ciderlocal" + lochash,
|
||||
_id: "ciderlocalart" + lochash,
|
||||
url: metadata.common.picture != undefined ? metadata.common.picture[0].data.toString('base64') : "",
|
||||
}
|
||||
metadatalistart.push(art)
|
||||
numid += 1;
|
||||
ProviderDB.db.putIfNotExists(form)
|
||||
ProviderDB.db.putIfNotExists(art)
|
||||
metadatalist.push(form)
|
||||
|
||||
if (this.localSongs.length === 0 && numid % 10 === 0) { // send updated chunks only if there is no previous database
|
||||
this.eventEmitter.emit('newtracks', metadatalist)}
|
||||
}
|
||||
} catch (e) {console.error("localfiles error:", e)}
|
||||
}
|
||||
// console.log('metadatalist', metadatalist);
|
||||
this.localSongs = metadatalist;
|
||||
this.localSongsArts = metadatalistart;
|
||||
return metadatalist;
|
||||
}
|
||||
static async getFiles(dir: any) {
|
||||
const dirents = await readdir(dir, { withFileTypes: true });
|
||||
const files = await Promise.all(dirents.map((dirent: any) => {
|
||||
const res = path.resolve(dir, dirent.name);
|
||||
return dirent.isDirectory() ? this.getFiles(res) : res;
|
||||
}));
|
||||
return Array.prototype.concat(...files);
|
||||
} catch (e) {
|
||||
console.error("localfiles error:", e);
|
||||
}
|
||||
}
|
||||
// console.log('metadatalist', metadatalist);
|
||||
this.localSongs = metadatalist;
|
||||
this.localSongsArts = metadatalistart;
|
||||
return metadatalist;
|
||||
}
|
||||
static async getFiles(dir: any) {
|
||||
const dirents = await readdir(dir, { withFileTypes: true });
|
||||
const files = await Promise.all(
|
||||
dirents.map((dirent: any) => {
|
||||
const res = path.resolve(dir, dirent.name);
|
||||
return dirent.isDirectory() ? this.getFiles(res) : res;
|
||||
})
|
||||
);
|
||||
return Array.prototype.concat(...files);
|
||||
}
|
||||
|
||||
static async cleanUpDB(){
|
||||
let folders = utils.getStoreValue("libraryPrefs.localPaths")
|
||||
let rows = (await ProviderDB.db.allDocs({include_docs: true,
|
||||
attachments: true})).rows.map((item: any)=>{return item.doc})
|
||||
let tracks = rows.filter((item: any) => {return this.getDataType(item._id) == "track" && !folders.some((i: String) => {return item["attributes"]["assetUrl"].startsWith("file:///" + i)})})
|
||||
let hashs = tracks.map((i: any) => {return i._id})
|
||||
for (let hash of hashs){
|
||||
try{
|
||||
ProviderDB.db.get(hash).then(function (doc: any) {
|
||||
return ProviderDB.db.remove(doc);
|
||||
});} catch(e){}
|
||||
try{
|
||||
ProviderDB.db.get(hash.replace('ciderlocal','ciderlocalart')).then(function (doc: any) {
|
||||
return ProviderDB.db.remove(doc);
|
||||
});} catch(e){}
|
||||
}
|
||||
}
|
||||
|
||||
static setupHandlers () {
|
||||
const app = utils.getExpress()
|
||||
console.log("Setting up handlers for local files")
|
||||
app.get("/ciderlocal/:songs", (req: any, res: any) => {
|
||||
const audio = atob(req.params.songs.replace(/_/g, '/').replace(/-/g, '+'));
|
||||
//console.log('auss', audio)
|
||||
let data = {
|
||||
data:
|
||||
LocalFiles.localSongs.filter((f: any) => audio.split(',').includes(f.id))
|
||||
};
|
||||
res.send(data);
|
||||
static async cleanUpDB() {
|
||||
let folders = utils.getStoreValue("libraryPrefs.localPaths");
|
||||
let rows = (await ProviderDB.db.allDocs({ include_docs: true, attachments: true })).rows.map((item: any) => {
|
||||
return item.doc;
|
||||
});
|
||||
let tracks = rows.filter((item: any) => {
|
||||
return (
|
||||
this.getDataType(item._id) == "track" &&
|
||||
!folders.some((i: String) => {
|
||||
return item["attributes"]["assetUrl"].startsWith("file:///" + i);
|
||||
})
|
||||
);
|
||||
});
|
||||
let hashs = tracks.map((i: any) => {
|
||||
return i._id;
|
||||
});
|
||||
for (let hash of hashs) {
|
||||
try {
|
||||
ProviderDB.db.get(hash).then(function (doc: any) {
|
||||
return ProviderDB.db.remove(doc);
|
||||
});
|
||||
|
||||
app.get("/ciderlocalart/:songs", (req: any, res: any) => {
|
||||
const audio = req.params.songs;
|
||||
// metadata.common.picture[0].data.toString('base64')
|
||||
|
||||
res.setHeader('Cache-Control', 'public, max-age=31536000');
|
||||
res.setHeader('Expires', new Date(Date.now() + 31536000000).toUTCString());
|
||||
res.setHeader('Content-Type', 'image/jpeg');
|
||||
|
||||
let data =
|
||||
LocalFiles.localSongsArts.filter((f: any) => f.id == audio);
|
||||
res.status(200).send(Buffer.from(data[0]?.url, 'base64'));
|
||||
} catch (e) {}
|
||||
try {
|
||||
ProviderDB.db.get(hash.replace("ciderlocal", "ciderlocalart")).then(function (doc: any) {
|
||||
return ProviderDB.db.remove(doc);
|
||||
});
|
||||
|
||||
return app
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static setupHandlers() {
|
||||
const app = utils.getExpress();
|
||||
console.log("Setting up handlers for local files");
|
||||
app.get("/ciderlocal/:songs", (req: any, res: any) => {
|
||||
const audio = atob(req.params.songs.replace(/_/g, "/").replace(/-/g, "+"));
|
||||
//console.log('auss', audio)
|
||||
let data = {
|
||||
data: LocalFiles.localSongs.filter((f: any) => audio.split(",").includes(f.id)),
|
||||
};
|
||||
res.send(data);
|
||||
});
|
||||
|
||||
app.get("/ciderlocalart/:songs", (req: any, res: any) => {
|
||||
const audio = req.params.songs;
|
||||
// metadata.common.picture[0].data.toString('base64')
|
||||
|
||||
res.setHeader("Cache-Control", "public, max-age=31536000");
|
||||
res.setHeader("Expires", new Date(Date.now() + 31536000000).toUTCString());
|
||||
res.setHeader("Content-Type", "image/jpeg");
|
||||
|
||||
let data = LocalFiles.localSongsArts.filter((f: any) => f.id == audio);
|
||||
res.status(200).send(Buffer.from(data[0]?.url, "base64"));
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,185 +1,182 @@
|
|||
global.ipcRenderer = require('electron').ipcRenderer;
|
||||
console.info('Loaded Preload')
|
||||
global.ipcRenderer = require("electron").ipcRenderer;
|
||||
console.info("Loaded Preload");
|
||||
|
||||
let cache = {playParams: {id: 0}, status: null, remainingTime: 0},
|
||||
playbackCache = {status: null, time: Date.now()};
|
||||
let cache = { playParams: { id: 0 }, status: null, remainingTime: 0 },
|
||||
playbackCache = { status: null, time: Date.now() };
|
||||
|
||||
const MusicKitInterop = {
|
||||
init: function () {
|
||||
/* MusicKit.Events.playbackStateDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackStateDidChange, () => {
|
||||
const attributes = MusicKitInterop.getAttributes()
|
||||
if (MusicKitInterop.filterTrack(attributes, true, false)) {
|
||||
global.ipcRenderer.send('playbackStateDidChange', attributes)
|
||||
global.ipcRenderer.send('wsapi-updatePlaybackState', attributes);
|
||||
}
|
||||
});
|
||||
init: function () {
|
||||
/* MusicKit.Events.playbackStateDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackStateDidChange, () => {
|
||||
const attributes = MusicKitInterop.getAttributes();
|
||||
if (MusicKitInterop.filterTrack(attributes, true, false)) {
|
||||
global.ipcRenderer.send("playbackStateDidChange", attributes);
|
||||
global.ipcRenderer.send("wsapi-updatePlaybackState", attributes);
|
||||
}
|
||||
});
|
||||
|
||||
/* MusicKit.Events.playbackProgressDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackProgressDidChange, async () => {
|
||||
const attributes = MusicKitInterop.getAttributes()
|
||||
// wsapi call
|
||||
ipcRenderer.send('wsapi-updatePlaybackState', attributes);
|
||||
// lastfm call
|
||||
if (app.mk.currentPlaybackProgress === (app.cfg.connectivity.lastfm.scrobble_after / 100)) {
|
||||
attributes.primaryArtist = (app.cfg.connectivity.lastfm.enabled && app.cfg.connectivity.lastfm.remove_featured) ? await this.fetchPrimaryArtist(attributes.artistName) : attributes.artistName;
|
||||
ipcRenderer.send('lastfm:scrobbleTrack', attributes);
|
||||
}
|
||||
});
|
||||
/* MusicKit.Events.playbackProgressDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackProgressDidChange, async () => {
|
||||
const attributes = MusicKitInterop.getAttributes();
|
||||
// wsapi call
|
||||
ipcRenderer.send("wsapi-updatePlaybackState", attributes);
|
||||
// lastfm call
|
||||
if (app.mk.currentPlaybackProgress === app.cfg.connectivity.lastfm.scrobble_after / 100) {
|
||||
attributes.primaryArtist = app.cfg.connectivity.lastfm.enabled && app.cfg.connectivity.lastfm.remove_featured ? await this.fetchPrimaryArtist(attributes.artistName) : attributes.artistName;
|
||||
ipcRenderer.send("lastfm:scrobbleTrack", attributes);
|
||||
}
|
||||
});
|
||||
|
||||
/* MusicKit.Events.playbackTimeDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackTimeDidChange, () => {
|
||||
ipcRenderer.send('mpris:playbackTimeDidChange', (MusicKit.getInstance()?.currentPlaybackTime * 1000 * 1000) ?? 0);
|
||||
});
|
||||
/* MusicKit.Events.playbackTimeDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackTimeDidChange, () => {
|
||||
ipcRenderer.send("mpris:playbackTimeDidChange", MusicKit.getInstance()?.currentPlaybackTime * 1000 * 1000 ?? 0);
|
||||
});
|
||||
|
||||
/* MusicKit.Events.nowPlayingItemDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.nowPlayingItemDidChange, async () => {
|
||||
console.debug('[cider:preload] nowPlayingItemDidChange')
|
||||
const attributes = MusicKitInterop.getAttributes()
|
||||
attributes.primaryArtist = (app.cfg.connectivity.lastfm.enabled && app.cfg.connectivity.lastfm.remove_featured) ? await this.fetchPrimaryArtist(attributes.artistName) : attributes.artistName;
|
||||
/* MusicKit.Events.nowPlayingItemDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.nowPlayingItemDidChange, async () => {
|
||||
console.debug("[cider:preload] nowPlayingItemDidChange");
|
||||
const attributes = MusicKitInterop.getAttributes();
|
||||
attributes.primaryArtist = app.cfg.connectivity.lastfm.enabled && app.cfg.connectivity.lastfm.remove_featured ? await this.fetchPrimaryArtist(attributes.artistName) : attributes.artistName;
|
||||
|
||||
if (MusicKitInterop.filterTrack(attributes, false, true)) {
|
||||
global.ipcRenderer.send('nowPlayingItemDidChange', attributes);
|
||||
} else if (attributes.name !== 'no-title-found' && attributes.playParams.id !== "no-id-found") {
|
||||
global.ipcRenderer.send('lastfm:nowPlayingChange', attributes);
|
||||
}
|
||||
if (MusicKitInterop.filterTrack(attributes, false, true)) {
|
||||
global.ipcRenderer.send("nowPlayingItemDidChange", attributes);
|
||||
} else if (attributes.name !== "no-title-found" && attributes.playParams.id !== "no-id-found") {
|
||||
global.ipcRenderer.send("lastfm:nowPlayingChange", attributes);
|
||||
}
|
||||
|
||||
if (app.cfg.general.playbackNotifications && !document.hasFocus() && attributes.artistName && attributes.artwork && attributes.name) {
|
||||
global.ipcRenderer.send('playbackNotifications:create', attributes);
|
||||
}
|
||||
if (app.cfg.general.playbackNotifications && !document.hasFocus() && attributes.artistName && attributes.artwork && attributes.name) {
|
||||
global.ipcRenderer.send("playbackNotifications:create", attributes);
|
||||
}
|
||||
|
||||
if (MusicKit.getInstance().nowPlayingItem) {
|
||||
await this.sleep(750);
|
||||
MusicKit.getInstance().playbackRate = app.cfg.audio.playbackRate;
|
||||
}
|
||||
});
|
||||
if (MusicKit.getInstance().nowPlayingItem) {
|
||||
await this.sleep(750);
|
||||
MusicKit.getInstance().playbackRate = app.cfg.audio.playbackRate;
|
||||
}
|
||||
});
|
||||
|
||||
/* MusicKit.Events.authorizationStatusDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.authorizationStatusDidChange, () => {
|
||||
global.ipcRenderer.send('authorizationStatusDidChange', MusicKit.getInstance().authorizationStatus)
|
||||
});
|
||||
/* MusicKit.Events.authorizationStatusDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.authorizationStatusDidChange, () => {
|
||||
global.ipcRenderer.send("authorizationStatusDidChange", MusicKit.getInstance().authorizationStatus);
|
||||
});
|
||||
|
||||
/* MusicKit.Events.mediaPlaybackError */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.mediaPlaybackError, (e) => {
|
||||
console.warn(`[cider:preload] mediaPlaybackError] ${e}`);
|
||||
});
|
||||
/* MusicKit.Events.mediaPlaybackError */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.mediaPlaybackError, (e) => {
|
||||
console.warn(`[cider:preload] mediaPlaybackError] ${e}`);
|
||||
});
|
||||
|
||||
/* MusicKit.Events.shuffleModeDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.shuffleModeDidChange, () => {
|
||||
global.ipcRenderer.send('shuffleModeDidChange', MusicKit.getInstance().shuffleMode)
|
||||
});
|
||||
/* MusicKit.Events.shuffleModeDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.shuffleModeDidChange, () => {
|
||||
global.ipcRenderer.send("shuffleModeDidChange", MusicKit.getInstance().shuffleMode);
|
||||
});
|
||||
|
||||
/* MusicKit.Events.repeatModeDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.repeatModeDidChange, () => {
|
||||
global.ipcRenderer.send('repeatModeDidChange', MusicKit.getInstance().repeatMode)
|
||||
});
|
||||
},
|
||||
/* MusicKit.Events.repeatModeDidChange */
|
||||
MusicKit.getInstance().addEventListener(MusicKit.Events.repeatModeDidChange, () => {
|
||||
global.ipcRenderer.send("repeatModeDidChange", MusicKit.getInstance().repeatMode);
|
||||
});
|
||||
},
|
||||
|
||||
sleep(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
},
|
||||
sleep(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
},
|
||||
|
||||
async fetchPrimaryArtist(artist) {
|
||||
if (app.mk.nowPlayingItem?.relationships?.artists) {
|
||||
const artist = await app.mk.api.artist(app.mk.nowPlayingItem.relationships.artists.data[0].id)
|
||||
return artist.attributes.name
|
||||
} else {
|
||||
return artist
|
||||
}
|
||||
},
|
||||
async fetchPrimaryArtist(artist) {
|
||||
if (app.mk.nowPlayingItem?.relationships?.artists) {
|
||||
const artist = await app.mk.api.artist(app.mk.nowPlayingItem.relationships.artists.data[0].id);
|
||||
return artist.attributes.name;
|
||||
} else {
|
||||
return artist;
|
||||
}
|
||||
},
|
||||
|
||||
getAttributes: function () {
|
||||
const mk = MusicKit.getInstance()
|
||||
const nowPlayingItem = mk.nowPlayingItem;
|
||||
const isPlayingExport = mk.isPlaying;
|
||||
const remainingTimeExport = mk.currentPlaybackTimeRemaining;
|
||||
const currentPlaybackProgress = mk.currentPlaybackProgress;
|
||||
const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {});
|
||||
getAttributes: function () {
|
||||
const mk = MusicKit.getInstance();
|
||||
const nowPlayingItem = mk.nowPlayingItem;
|
||||
const isPlayingExport = mk.isPlaying;
|
||||
const remainingTimeExport = mk.currentPlaybackTimeRemaining;
|
||||
const currentPlaybackProgress = mk.currentPlaybackProgress;
|
||||
const attributes = nowPlayingItem != null ? nowPlayingItem.attributes : {};
|
||||
|
||||
attributes.songId = attributes.songId ?? attributes.playParams?.catalogId ?? attributes.playParams?.id
|
||||
attributes.status = isPlayingExport ?? null;
|
||||
attributes.name = attributes?.name ?? 'no-title-found';
|
||||
attributes.artwork = attributes?.artwork ?? {url: ''};
|
||||
attributes.artwork.url = (attributes?.artwork?.url ?? '').replace(`{f}`, "png");
|
||||
attributes.playParams = attributes?.playParams ?? {id: 'no-id-found'};
|
||||
attributes.playParams.id = attributes?.playParams?.id ?? 'no-id-found';
|
||||
attributes.url = {
|
||||
cider: `https://cider.sh/link?play/s/${nowPlayingItem?._songId ?? (nowPlayingItem?.songId ?? 'no-id-found')}`,
|
||||
appleMusic: attributes.websiteUrl ? attributes.websiteUrl : `https://music.apple.com/${mk.storefrontId}/song/${nowPlayingItem?._songId ?? (nowPlayingItem?.songId ?? 'no-id-found')}`
|
||||
}
|
||||
if (attributes.playParams.id === 'no-id-found') {
|
||||
attributes.playParams.id = nowPlayingItem?.id ?? 'no-id-found';
|
||||
}
|
||||
attributes.albumName = attributes?.albumName ?? '';
|
||||
attributes.artistName = attributes?.artistName ?? '';
|
||||
attributes.genreNames = attributes?.genreNames ?? [];
|
||||
attributes.remainingTime = remainingTimeExport
|
||||
? remainingTimeExport * 1000
|
||||
: 0;
|
||||
attributes.durationInMillis = attributes?.durationInMillis ?? 0;
|
||||
attributes.currentPlaybackTime = mk?.currentPlaybackTime ?? 0;
|
||||
attributes.currentPlaybackProgress = currentPlaybackProgress ?? 0;
|
||||
attributes.startTime = Date.now();
|
||||
attributes.endTime = Math.round(
|
||||
attributes?.playParams?.id === cache.playParams.id
|
||||
? Date.now() + attributes?.remainingTime
|
||||
: attributes?.startTime + attributes?.durationInMillis
|
||||
);
|
||||
return attributes;
|
||||
},
|
||||
attributes.songId = attributes.songId ?? attributes.playParams?.catalogId ?? attributes.playParams?.id;
|
||||
attributes.status = isPlayingExport ?? null;
|
||||
attributes.name = attributes?.name ?? "no-title-found";
|
||||
attributes.artwork = attributes?.artwork ?? { url: "" };
|
||||
attributes.artwork.url = (attributes?.artwork?.url ?? "").replace(`{f}`, "png");
|
||||
attributes.playParams = attributes?.playParams ?? { id: "no-id-found" };
|
||||
attributes.playParams.id = attributes?.playParams?.id ?? "no-id-found";
|
||||
attributes.url = {
|
||||
cider: `https://cider.sh/link?play/s/${nowPlayingItem?._songId ?? nowPlayingItem?.songId ?? "no-id-found"}`,
|
||||
appleMusic: attributes.websiteUrl ? attributes.websiteUrl : `https://music.apple.com/${mk.storefrontId}/song/${nowPlayingItem?._songId ?? nowPlayingItem?.songId ?? "no-id-found"}`,
|
||||
};
|
||||
if (attributes.playParams.id === "no-id-found") {
|
||||
attributes.playParams.id = nowPlayingItem?.id ?? "no-id-found";
|
||||
}
|
||||
attributes.albumName = attributes?.albumName ?? "";
|
||||
attributes.artistName = attributes?.artistName ?? "";
|
||||
attributes.genreNames = attributes?.genreNames ?? [];
|
||||
attributes.remainingTime = remainingTimeExport ? remainingTimeExport * 1000 : 0;
|
||||
attributes.durationInMillis = attributes?.durationInMillis ?? 0;
|
||||
attributes.currentPlaybackTime = mk?.currentPlaybackTime ?? 0;
|
||||
attributes.currentPlaybackProgress = currentPlaybackProgress ?? 0;
|
||||
attributes.startTime = Date.now();
|
||||
attributes.endTime = Math.round(attributes?.playParams?.id === cache.playParams.id ? Date.now() + attributes?.remainingTime : attributes?.startTime + attributes?.durationInMillis);
|
||||
return attributes;
|
||||
},
|
||||
|
||||
filterTrack: function (a, playbackCheck, mediaCheck) {
|
||||
if (a.name === 'no-title-found' || a.playParams.id === "no-id-found") {
|
||||
return;
|
||||
} else if (mediaCheck && a.playParams.id === cache.playParams.id) {
|
||||
return;
|
||||
} else if (playbackCheck && a.status === playbackCache.status) {
|
||||
return;
|
||||
} else if (playbackCheck && !a.status && a.remainingTime === playbackCache.time) { /* Pretty much have to do this to prevent multiple runs when a song starts playing */
|
||||
return;
|
||||
}
|
||||
cache = a;
|
||||
if (playbackCheck) playbackCache = {status: a.status, time: a.remainingTime};
|
||||
return true;
|
||||
},
|
||||
filterTrack: function (a, playbackCheck, mediaCheck) {
|
||||
if (a.name === "no-title-found" || a.playParams.id === "no-id-found") {
|
||||
return;
|
||||
} else if (mediaCheck && a.playParams.id === cache.playParams.id) {
|
||||
return;
|
||||
} else if (playbackCheck && a.status === playbackCache.status) {
|
||||
return;
|
||||
} else if (playbackCheck && !a.status && a.remainingTime === playbackCache.time) {
|
||||
/* Pretty much have to do this to prevent multiple runs when a song starts playing */
|
||||
return;
|
||||
}
|
||||
cache = a;
|
||||
if (playbackCheck) playbackCache = { status: a.status, time: a.remainingTime };
|
||||
return true;
|
||||
},
|
||||
|
||||
play: () => {
|
||||
MusicKit.getInstance().play().catch(console.error);
|
||||
},
|
||||
play: () => {
|
||||
MusicKit.getInstance().play().catch(console.error);
|
||||
},
|
||||
|
||||
pause: () => {
|
||||
MusicKit.getInstance().pause();
|
||||
},
|
||||
pause: () => {
|
||||
MusicKit.getInstance().pause();
|
||||
},
|
||||
|
||||
playPause: () => {
|
||||
if (MusicKit.getInstance().isPlaying) {
|
||||
MusicKit.getInstance().pause();
|
||||
} else if (MusicKit.getInstance().nowPlayingItem != null) {
|
||||
MusicKit.getInstance().play().catch(console.error);
|
||||
}
|
||||
},
|
||||
playPause: () => {
|
||||
if (MusicKit.getInstance().isPlaying) {
|
||||
MusicKit.getInstance().pause();
|
||||
} else if (MusicKit.getInstance().nowPlayingItem != null) {
|
||||
MusicKit.getInstance().play().catch(console.error);
|
||||
}
|
||||
},
|
||||
|
||||
next: () => {
|
||||
// try {
|
||||
// app.prevButtonBackIndicator = false;
|
||||
// } catch (e) { }
|
||||
// if (MusicKit.getInstance().queue.nextPlayableItemIndex != -1 && MusicKit.getInstance().queue.nextPlayableItemIndex != null)
|
||||
// MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.nextPlayableItemIndex);
|
||||
MusicKit.getInstance().skipToNextItem().then(r => console.debug(`[cider:preload] [next] Skipping to Next ${r}`));
|
||||
},
|
||||
next: () => {
|
||||
// try {
|
||||
// app.prevButtonBackIndicator = false;
|
||||
// } catch (e) { }
|
||||
// if (MusicKit.getInstance().queue.nextPlayableItemIndex != -1 && MusicKit.getInstance().queue.nextPlayableItemIndex != null)
|
||||
// MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.nextPlayableItemIndex);
|
||||
MusicKit.getInstance()
|
||||
.skipToNextItem()
|
||||
.then((r) => console.debug(`[cider:preload] [next] Skipping to Next ${r}`));
|
||||
},
|
||||
|
||||
previous: () => {
|
||||
// if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null)
|
||||
// MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex);
|
||||
MusicKit.getInstance().skipToPreviousItem().then(r => console.debug(`[cider:preload] [previous] Skipping to Previous ${r}`));
|
||||
}
|
||||
previous: () => {
|
||||
// if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null)
|
||||
// MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex);
|
||||
MusicKit.getInstance()
|
||||
.skipToPreviousItem()
|
||||
.then((r) => console.debug(`[cider:preload] [previous] Skipping to Previous ${r}`));
|
||||
},
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
process.once('loaded', () => {
|
||||
console.debug("[cider:preload] IPC Listeners Created!")
|
||||
global.MusicKitInterop = MusicKitInterop;
|
||||
process.once("loaded", () => {
|
||||
console.debug("[cider:preload] IPC Listeners Created!");
|
||||
global.MusicKitInterop = MusicKitInterop;
|
||||
});
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"js": {
|
||||
"beautify.ignore": "src/renderer/index.js"
|
||||
}
|
||||
}
|
|
@ -1,154 +1,136 @@
|
|||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-Thin.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Thin.woff?v=3.19") format("woff");
|
||||
src: url("Inter-Thin.woff2?v=3.19") format("woff2"), url("Inter-Thin.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-ThinItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-ThinItalic.woff?v=3.19") format("woff");
|
||||
src: url("Inter-ThinItalic.woff2?v=3.19") format("woff2"), url("Inter-ThinItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLight.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-ExtraLight.woff?v=3.19") format("woff");
|
||||
src: url("Inter-ExtraLight.woff2?v=3.19") format("woff2"), url("Inter-ExtraLight.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLightItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-ExtraLightItalic.woff?v=3.19") format("woff");
|
||||
src: url("Inter-ExtraLightItalic.woff2?v=3.19") format("woff2"), url("Inter-ExtraLightItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-Light.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Light.woff?v=3.19") format("woff");
|
||||
src: url("Inter-Light.woff2?v=3.19") format("woff2"), url("Inter-Light.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-LightItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-LightItalic.woff?v=3.19") format("woff");
|
||||
src: url("Inter-LightItalic.woff2?v=3.19") format("woff2"), url("Inter-LightItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Regular.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Regular.woff?v=3.19") format("woff");
|
||||
src: url("Inter-Regular.woff2?v=3.19") format("woff2"), url("Inter-Regular.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Italic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Italic.woff?v=3.19") format("woff");
|
||||
src: url("Inter-Italic.woff2?v=3.19") format("woff2"), url("Inter-Italic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-Medium.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Medium.woff?v=3.19") format("woff");
|
||||
src: url("Inter-Medium.woff2?v=3.19") format("woff2"), url("Inter-Medium.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-MediumItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-MediumItalic.woff?v=3.19") format("woff");
|
||||
src: url("Inter-MediumItalic.woff2?v=3.19") format("woff2"), url("Inter-MediumItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBold.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-SemiBold.woff?v=3.19") format("woff");
|
||||
src: url("Inter-SemiBold.woff2?v=3.19") format("woff2"), url("Inter-SemiBold.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBoldItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-SemiBoldItalic.woff?v=3.19") format("woff");
|
||||
src: url("Inter-SemiBoldItalic.woff2?v=3.19") format("woff2"), url("Inter-SemiBoldItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-Bold.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Bold.woff?v=3.19") format("woff");
|
||||
src: url("Inter-Bold.woff2?v=3.19") format("woff2"), url("Inter-Bold.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-BoldItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-BoldItalic.woff?v=3.19") format("woff");
|
||||
src: url("Inter-BoldItalic.woff2?v=3.19") format("woff2"), url("Inter-BoldItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBold.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-ExtraBold.woff?v=3.19") format("woff");
|
||||
src: url("Inter-ExtraBold.woff2?v=3.19") format("woff2"), url("Inter-ExtraBold.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBoldItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-ExtraBoldItalic.woff?v=3.19") format("woff");
|
||||
src: url("Inter-ExtraBoldItalic.woff2?v=3.19") format("woff2"), url("Inter-ExtraBoldItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-Black.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Black.woff?v=3.19") format("woff");
|
||||
src: url("Inter-Black.woff2?v=3.19") format("woff2"), url("Inter-Black.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-BlackItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-BlackItalic.woff?v=3.19") format("woff");
|
||||
src: url("Inter-BlackItalic.woff2?v=3.19") format("woff2"), url("Inter-BlackItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------
|
||||
|
@ -161,23 +143,22 @@ Usage:
|
|||
}
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter var';
|
||||
font-family: "Inter var";
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-named-instance: 'Regular';
|
||||
font-named-instance: "Regular";
|
||||
src: url("Inter-roman.var.woff2?v=3.19") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter var';
|
||||
font-family: "Inter var";
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
font-named-instance: 'Italic';
|
||||
font-named-instance: "Italic";
|
||||
src: url("Inter-italic.var.woff2?v=3.19") format("woff2");
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
[EXPERIMENTAL] Multi-axis, single variable font.
|
||||
|
||||
|
@ -192,7 +173,7 @@ explicitly, e.g.
|
|||
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter var experimental';
|
||||
font-family: "Inter var experimental";
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: oblique 0deg 10deg;
|
||||
|
|
|
@ -8,9 +8,9 @@ http://scripts.sil.org/OFL
|
|||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'Pretendard Variable';
|
||||
font-weight: 45 920;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
src: local('Pretendard Variable'), url('./woff2/PretendardVariable.woff2') format('woff2-variations');
|
||||
font-family: "Pretendard Variable";
|
||||
font-weight: 45 920;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
src: local("Pretendard Variable"), url("./woff2/PretendardVariable.woff2") format("woff2-variations");
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
52902
src/renderer/hlscider.js
52902
src/renderer/hlscider.js
File diff suppressed because it is too large
Load diff
|
@ -57,11 +57,11 @@ Vue.component("animated-number", {
|
|||
});
|
||||
|
||||
function initMusicKit() {
|
||||
if(!this.responseText) {
|
||||
console.log("Using stored token")
|
||||
if (!this.responseText) {
|
||||
console.log("Using stored token");
|
||||
this.responseText = JSON.stringify({
|
||||
token: localStorage.getItem("lastToken")
|
||||
})
|
||||
token: localStorage.getItem("lastToken"),
|
||||
});
|
||||
}
|
||||
let parsedJson = JSON.parse(this.responseText);
|
||||
localStorage.setItem("lastToken", parsedJson.token);
|
||||
|
@ -95,10 +95,10 @@ function capiInit() {
|
|||
request.addEventListener("load", initMusicKit);
|
||||
request.onreadystatechange = function (aEvt) {
|
||||
if (request.readyState == 4 && request.status != 200) {
|
||||
if(localStorage.getItem("lastToken") != null) {
|
||||
initMusicKit()
|
||||
if (localStorage.getItem("lastToken") != null) {
|
||||
initMusicKit();
|
||||
} else {
|
||||
console.error(`Failed to load capi, cannot get token [${request.status}]`)
|
||||
console.error(`Failed to load capi, cannot get token [${request.status}]`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -110,7 +110,7 @@ document.addEventListener("musickitloaded", function () {
|
|||
if (showOobe()) return;
|
||||
console.log("MusicKit loaded");
|
||||
// MusicKit global is now defined
|
||||
capiInit()
|
||||
capiInit();
|
||||
});
|
||||
window.addEventListener("drmUnsupported", function () {
|
||||
initMusicKit();
|
||||
|
@ -140,12 +140,7 @@ function Clone(obj) {
|
|||
}
|
||||
|
||||
function uuidv4() {
|
||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
|
||||
(
|
||||
c ^
|
||||
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
|
||||
).toString(16)
|
||||
);
|
||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
|
||||
}
|
||||
|
||||
function xmlToJson(xml) {
|
||||
|
@ -196,26 +191,19 @@ async function asyncForEach(array, callback) {
|
|||
|
||||
var checkIfScrollIsStatic = setInterval(() => {
|
||||
try {
|
||||
if (
|
||||
position === document.getElementsByClassName("lyric-body")[0].scrollTop
|
||||
) {
|
||||
if (position === document.getElementsByClassName("lyric-body")[0].scrollTop) {
|
||||
clearInterval(checkIfScrollIsStatic);
|
||||
// do something
|
||||
}
|
||||
position = document.getElementsByClassName("lyric-body")[0].scrollTop;
|
||||
} catch (e) { }
|
||||
} catch (e) {}
|
||||
}, 50);
|
||||
|
||||
// WebGPU Console Notification
|
||||
async function webGPU() {
|
||||
try {
|
||||
const currentGPU = await navigator.gpu.requestAdapter();
|
||||
console.log(
|
||||
"WebGPU enabled on",
|
||||
currentGPU.name,
|
||||
"with feature ID",
|
||||
currentGPU.features.size
|
||||
);
|
||||
console.log("WebGPU enabled on", currentGPU.name, "with feature ID", currentGPU.features.size);
|
||||
} catch (e) {
|
||||
console.log("WebGPU disabled / WebGPU initialization failed");
|
||||
}
|
||||
|
@ -240,9 +228,9 @@ function isJson(item) {
|
|||
webGPU().then();
|
||||
|
||||
function showOobe() {
|
||||
return false
|
||||
return false;
|
||||
if (localStorage.getItem("music.ampwebplay.media-user-token") && localStorage.getItem("seenOOBE")) {
|
||||
return false
|
||||
return false;
|
||||
} else {
|
||||
function waitForApp() {
|
||||
if (typeof app.init !== "undefined") {
|
||||
|
@ -252,7 +240,7 @@ function showOobe() {
|
|||
}
|
||||
}
|
||||
waitForApp();
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,13 +254,7 @@ document.addEventListener("DOMContentLoaded", async function () {
|
|||
document.addEventListener(
|
||||
"contextmenu",
|
||||
function (e) {
|
||||
if (
|
||||
e.target.tagName.toLowerCase() == "textarea" ||
|
||||
(e.target.tagName.toLowerCase() == "input" &&
|
||||
e.target.type != "checkbox" &&
|
||||
e.target.type != "radio" &&
|
||||
e.target.disabled == false)
|
||||
) {
|
||||
if (e.target.tagName.toLowerCase() == "textarea" || (e.target.tagName.toLowerCase() == "input" && e.target.type != "checkbox" && e.target.type != "radio" && e.target.disabled == false)) {
|
||||
e.preventDefault();
|
||||
const menuPanel = {
|
||||
items: {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
|||
@colorMixRate: 1%;
|
||||
@transparencyRate: 50%;
|
||||
|
||||
@keyColor : #fc3c44;
|
||||
@keyColor: #fc3c44;
|
||||
@ciderColor: #ff2654;
|
||||
@baseColor: #1e1e1e;
|
||||
@baseColorMix: mix(@baseColor, transparent, @transparencyRate);
|
||||
|
@ -10,12 +10,12 @@
|
|||
@appOpacity: 0.15;
|
||||
|
||||
:root {
|
||||
--baseColor: @baseColor;
|
||||
--baseColorMix: @baseColorMix;
|
||||
--sidebarColor: @sidebarColor;
|
||||
--sidebarColorMix: @sidebarColorMix;
|
||||
--ciderColor: @ciderColor;
|
||||
--appOpacity: @appOpacity;
|
||||
--transparencyRate: @transparencyRate;
|
||||
--macOSChromeColor: rgb(14 14 14 / 32%);
|
||||
}
|
||||
--baseColor: @baseColor;
|
||||
--baseColorMix: @baseColorMix;
|
||||
--sidebarColor: @sidebarColor;
|
||||
--sidebarColorMix: @sidebarColorMix;
|
||||
--ciderColor: @ciderColor;
|
||||
--appOpacity: @appOpacity;
|
||||
--transparencyRate: @transparencyRate;
|
||||
--macOSChromeColor: rgb(14 14 14 / 32%);
|
||||
}
|
||||
|
|
1994
src/renderer/less/bootstrap.less
vendored
1994
src/renderer/less/bootstrap.less
vendored
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,7 @@
|
|||
zoom: 0.95;
|
||||
}
|
||||
.app-sidebar-content {
|
||||
padding:0px;
|
||||
padding: 0px;
|
||||
|
||||
.app-sidebar-header-text {
|
||||
padding: 6px 10px;
|
||||
|
@ -56,4 +56,4 @@
|
|||
#app-content {
|
||||
// zoom: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,36 +5,35 @@
|
|||
#app.twopanel {
|
||||
--chromeHeight1: 46px;
|
||||
--chromeHeight2: 90px;
|
||||
--chromeHeight : calc(var(--chromeHeight1) + var(--chromeHeight2));
|
||||
--chromeHeight: calc(var(--chromeHeight1) + var(--chromeHeight2));
|
||||
|
||||
.modular-fs .app-drawer .lyric-footer {
|
||||
bottom: var(--chromeHeight2);
|
||||
}
|
||||
|
||||
.app-chrome {
|
||||
|
||||
&:not(.chrome-bottom) {
|
||||
.app-chrome--center {
|
||||
flex: 1;
|
||||
|
||||
.top-nav-group {
|
||||
background : #1e1e1e99;
|
||||
border : 1px solid lighten(@baseColor, 8);
|
||||
background: #1e1e1e99;
|
||||
border: 1px solid lighten(@baseColor, 8);
|
||||
border-radius: 12px;
|
||||
display : flex;
|
||||
height : 32px;
|
||||
display: flex;
|
||||
height: 32px;
|
||||
|
||||
.app-sidebar-item {
|
||||
background-color: #1e1e1e00;
|
||||
border-radius : 10px !important;
|
||||
border : 0px;
|
||||
min-width : 120px;
|
||||
padding : 6px;
|
||||
justify-content : center;
|
||||
align-items : center;
|
||||
margin : 0px;
|
||||
height : 100%;
|
||||
position : relative;
|
||||
border-radius: 10px !important;
|
||||
border: 0px;
|
||||
min-width: 120px;
|
||||
padding: 6px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
|
||||
._svg-icon {
|
||||
|
@ -42,18 +41,18 @@
|
|||
}
|
||||
|
||||
&:before {
|
||||
--dist : 1px;
|
||||
content : '';
|
||||
position : absolute;
|
||||
top : var(--dist);
|
||||
left : var(--dist);
|
||||
right : var(--dist);
|
||||
bottom : var(--dist);
|
||||
--dist: 1px;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: var(--dist);
|
||||
left: var(--dist);
|
||||
right: var(--dist);
|
||||
bottom: var(--dist);
|
||||
background-color: #fff;
|
||||
opacity : 0;
|
||||
border-radius : 10px;
|
||||
transform : scale(0.5);
|
||||
transition : transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||
opacity: 0;
|
||||
border-radius: 10px;
|
||||
transform: scale(0.5);
|
||||
transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
&:after {
|
||||
|
@ -65,8 +64,8 @@
|
|||
|
||||
&:before {
|
||||
transition: transform 0.1s ease-in-out, opacity 0.1s ease-in-out;
|
||||
opacity : .1;
|
||||
transform : scale(1);
|
||||
opacity: 0.1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,15 +73,15 @@
|
|||
background-color: transparent;
|
||||
|
||||
&:before {
|
||||
opacity : .2;
|
||||
opacity: 0.2;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
&.md-btn-primary {
|
||||
box-shadow : 0px 0px 0px 1px lighten(@baseColor, @colorMixRate * 8);
|
||||
box-shadow: 0px 0px 0px 1px lighten(@baseColor, @colorMixRate * 8);
|
||||
background-color: lighten(@baseColor, @colorMixRate * 5);
|
||||
z-index : 1;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +89,7 @@
|
|||
}
|
||||
|
||||
.app-mainmenu {
|
||||
width : 30px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
|
@ -101,11 +100,10 @@
|
|||
height: var(--chromeHeight1);
|
||||
|
||||
&.chrome-bottom {
|
||||
background : var(--color2);
|
||||
background: var(--color2);
|
||||
-webkit-app-region: no-drag;
|
||||
height : var(--chromeHeight2);
|
||||
box-shadow : 0px -2px 6px rgb(20 20 20 / 12%),
|
||||
0px -1px 0px 0px rgb(200 200 200 / 12%);
|
||||
height: var(--chromeHeight2);
|
||||
box-shadow: 0px -2px 6px rgb(20 20 20 / 12%), 0px -1px 0px 0px rgb(200 200 200 / 12%);
|
||||
z-index: 4;
|
||||
|
||||
.app-chrome-playback-duration-bottom {
|
||||
|
@ -116,33 +114,33 @@
|
|||
}
|
||||
|
||||
.col-sm-auto {
|
||||
width : 4em;
|
||||
display : flex;
|
||||
width: 4em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items : center;
|
||||
font-size : 0.8em;
|
||||
align-items: center;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
appearance : none;
|
||||
width : 100%;
|
||||
height : 5px;
|
||||
input[type="range"] {
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
background-color: rgb(200 200 200 / 10%);
|
||||
border-radius : 6px;
|
||||
box-shadow : 0px 0px 0px 1px rgba(0 0 0 / 10%);
|
||||
align-self : center;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0px 0px 0px 1px rgba(0 0 0 / 10%);
|
||||
align-self: center;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
opacity : 0;
|
||||
transform : scale(1);
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
-webkit-appearance: none;
|
||||
appearance : none;
|
||||
width : 16px;
|
||||
height : 16px;
|
||||
border-radius : 100%;
|
||||
background : var(--keyColor);
|
||||
cursor : default;
|
||||
transition : opacity .10s var(--appleEase), transform .10s var(--appleEase);
|
||||
appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 100%;
|
||||
background: var(--keyColor);
|
||||
cursor: default;
|
||||
transition: opacity 0.1s var(--appleEase), transform 0.1s var(--appleEase);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
@ -169,38 +167,37 @@
|
|||
.playback-button.play,
|
||||
.playback-button.pause,
|
||||
.playback-button.stop {
|
||||
width : 42px;
|
||||
height : 42px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
margin : 6px;
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
.app-chrome--center {
|
||||
display : flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.app-chrome-playback-controls {
|
||||
display : flex;
|
||||
z-index : 1;
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
// margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.app-chrome-playback-duration {
|
||||
position : relative;
|
||||
width : 80%;
|
||||
position: relative;
|
||||
width: 80%;
|
||||
-webkit-app-region: no-drag;
|
||||
height : 16px;
|
||||
height: 16px;
|
||||
|
||||
.song-progress {
|
||||
@bgColor : transparent;
|
||||
height : 16px;
|
||||
position : absolute;
|
||||
bottom : 4px;
|
||||
left : 0px;
|
||||
right : 4px;
|
||||
@bgColor: transparent;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 0px;
|
||||
right: 4px;
|
||||
background: @bgColor;
|
||||
z-index : 0;
|
||||
|
||||
z-index: 0;
|
||||
|
||||
.song-duration {
|
||||
display: flex;
|
||||
|
@ -208,64 +205,63 @@
|
|||
|
||||
.song-duration p {
|
||||
font-weight: 400;
|
||||
font-size : 10px;
|
||||
height : 1.2em;
|
||||
font-size: 10px;
|
||||
height: 1.2em;
|
||||
line-height: 1.3em;
|
||||
overflow : hidden;
|
||||
margin : 0 0 0 0.25em;
|
||||
overflow: hidden;
|
||||
margin: 0 0 0 0.25em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
>input[type=range] {
|
||||
> input[type="range"] {
|
||||
&::-webkit-slider-thumb {
|
||||
opacity : 1;
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
z-index : 1;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
appearance : none;
|
||||
width : 100%;
|
||||
height : 4px;
|
||||
input[type="range"] {
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: rgb(200 200 200 / 10%);
|
||||
border-radius : 2px;
|
||||
border-radius: 2px;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
opacity : 0;
|
||||
transform : scale(0.5);
|
||||
opacity: 0;
|
||||
transform: scale(0.5);
|
||||
-webkit-appearance: none;
|
||||
appearance : none;
|
||||
width : 12px;
|
||||
height : 12px;
|
||||
border-radius : 100%;
|
||||
background : var(--keyColor);
|
||||
cursor : default;
|
||||
transition : opacity .10s var(--appleEase), transform .10s var(--appleEase);
|
||||
appearance: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 100%;
|
||||
background: var(--keyColor);
|
||||
cursor: default;
|
||||
transition: opacity 0.1s var(--appleEase), transform 0.1s var(--appleEase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.app-chrome--left {
|
||||
width : 30%;
|
||||
justify-content : flex-start;
|
||||
align-items : flex-start;
|
||||
width: 30%;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
-webkit-app-region: no-drag !important;
|
||||
|
||||
.playback-controls {
|
||||
-webkit-app-region: no-drag !important;
|
||||
|
||||
.artwork {
|
||||
--offset : 20px;
|
||||
--offset: 20px;
|
||||
--marginOffset: 2;
|
||||
--size : calc(var(--chromeHeight2) - var(--offset));
|
||||
width : var(--size);
|
||||
height : var(--size);
|
||||
margin : 0 calc(var(--offset) / var(--marginOffset)) 0 calc(var(--offset) / var(--marginOffset));
|
||||
--size: calc(var(--chromeHeight2) - var(--offset));
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
margin: 0 calc(var(--offset) / var(--marginOffset)) 0 calc(var(--offset) / var(--marginOffset));
|
||||
|
||||
.mediaitem-artwork,
|
||||
img {
|
||||
|
@ -275,13 +271,13 @@
|
|||
|
||||
.playback-info {
|
||||
align-items: flex-start;
|
||||
margin : 6px;
|
||||
margin: 6px;
|
||||
|
||||
.song-name {
|
||||
text-align : left;
|
||||
font-size : 0.8em;
|
||||
font-weight : 500;
|
||||
width : 100%;
|
||||
text-align: left;
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
width: 100%;
|
||||
-webkit-mask-image: linear-gradient(-90deg, transparent 0%, transparent 10%, black 20%);
|
||||
}
|
||||
|
||||
|
@ -306,23 +302,22 @@
|
|||
|
||||
.song-artist-album-content {
|
||||
text-align: left;
|
||||
font-size : 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
width : 100%;
|
||||
height : 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
border : 0px;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.app-chrome--right {
|
||||
width : 30%;
|
||||
flex : 0 0 auto;
|
||||
width: 30%;
|
||||
flex: 0 0 auto;
|
||||
padding-right: 8px;
|
||||
}
|
||||
}
|
||||
|
@ -334,7 +329,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// screen width is less than 768px
|
||||
@media (max-width: 1100px) {
|
||||
#app.twopanel .app-chrome:not(.chrome-bottom) .app-chrome--center {
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Buttons
|
||||
.md-btn {
|
||||
font-family: inherit;
|
||||
|
@ -115,7 +114,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.md-close-btn {
|
||||
-webkit-mask-image: url("ameres://icons/webui/close.svg");
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
|
@ -165,7 +163,7 @@
|
|||
.page-btn {
|
||||
align-self: center;
|
||||
height: 32px;
|
||||
width:max-content;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.page-btn img {
|
||||
|
@ -174,19 +172,19 @@
|
|||
}
|
||||
|
||||
.md-ico-first {
|
||||
content: url('./assets/angles-left.svg');
|
||||
content: url("./assets/angles-left.svg");
|
||||
}
|
||||
|
||||
.md-ico-prev {
|
||||
content: url('./assets/chevron-left.svg');
|
||||
content: url("./assets/chevron-left.svg");
|
||||
}
|
||||
|
||||
.md-ico-next {
|
||||
content: url('./assets/chevron-right.svg');
|
||||
content: url("./assets/chevron-right.svg");
|
||||
}
|
||||
|
||||
.md-ico-last {
|
||||
content: url('./assets/angles-right.svg');
|
||||
content: url("./assets/angles-right.svg");
|
||||
}
|
||||
|
||||
.reload-btn {
|
||||
|
@ -234,7 +232,7 @@
|
|||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: rgb(200 200 200 / 10%)
|
||||
background: rgb(200 200 200 / 10%);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -315,7 +313,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#artworkLCD img {
|
||||
image-rendering: auto;
|
||||
}
|
||||
|
@ -514,7 +511,7 @@
|
|||
|
||||
.subtitle {
|
||||
width: 90%;
|
||||
font-size: .8em;
|
||||
font-size: 0.8em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
|
@ -570,39 +567,39 @@
|
|||
*/
|
||||
@keyframes load-bar {
|
||||
10% {
|
||||
box-shadow: inset 0 -4px 0
|
||||
box-shadow: inset 0 -4px 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
box-shadow: inset 0 -10px 0
|
||||
box-shadow: inset 0 -10px 0;
|
||||
}
|
||||
|
||||
30% {
|
||||
box-shadow: inset 0 -12px 0
|
||||
box-shadow: inset 0 -12px 0;
|
||||
}
|
||||
|
||||
40% {
|
||||
box-shadow: inset 0 -8px 0
|
||||
box-shadow: inset 0 -8px 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: inset 0 -4px 0
|
||||
box-shadow: inset 0 -4px 0;
|
||||
}
|
||||
|
||||
60% {
|
||||
box-shadow: inset 0 -6px 0
|
||||
box-shadow: inset 0 -6px 0;
|
||||
}
|
||||
|
||||
80% {
|
||||
box-shadow: inset 0 -12px 0
|
||||
box-shadow: inset 0 -12px 0;
|
||||
}
|
||||
|
||||
90% {
|
||||
box-shadow: inset 0 -6px 0
|
||||
box-shadow: inset 0 -6px 0;
|
||||
}
|
||||
|
||||
to {
|
||||
box-shadow: inset 0 -2px 0
|
||||
box-shadow: inset 0 -2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -629,17 +626,17 @@
|
|||
.loadbar-sound::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.loadbar-sound::before {
|
||||
left: -4.5px;
|
||||
animation-delay: -2.4s
|
||||
animation-delay: -2.4s;
|
||||
}
|
||||
|
||||
.loadbar-sound::after {
|
||||
right: -4.2px;
|
||||
animation-delay: -3.7s
|
||||
animation-delay: -3.7s;
|
||||
}
|
||||
|
||||
.isLibrary {
|
||||
|
@ -670,7 +667,6 @@
|
|||
box-shadow: var(--mediaItemShadow);
|
||||
}
|
||||
|
||||
|
||||
&:active {
|
||||
background: var(--selected-click);
|
||||
box-shadow: var(--mediaItemShadow);
|
||||
|
@ -805,7 +801,6 @@
|
|||
|
||||
&:hover + .cd-mediaitem-square-large-overlay {
|
||||
opacity: 1;
|
||||
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
@ -813,7 +808,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/* mediaitem-square-large */
|
||||
.cd-mediaitem-square-large {
|
||||
width: 190px;
|
||||
|
@ -855,12 +849,10 @@
|
|||
margin: 10px;
|
||||
margin-top: 0px;
|
||||
opacity: 0;
|
||||
|
||||
}
|
||||
|
||||
.cd-mediaitem-square-large-overlay > * {
|
||||
pointer-events: auto;
|
||||
|
||||
}
|
||||
|
||||
.cd-mediaitem-square-large > .cd-mediaitem-square-large-overlay {
|
||||
|
@ -873,15 +865,12 @@
|
|||
|
||||
.cd-mediaitem-square-large + .cd-mediaitem-square-large-overlay {
|
||||
pointer-events: none;
|
||||
|
||||
}
|
||||
|
||||
.cd-mediaitem-square-large:hover + .cd-mediaitem-square-large-overlay {
|
||||
opacity: 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.cd-mediaitem-square-large .artwork.round {
|
||||
border-radius: var(--mediaItemRadiusRound);
|
||||
}
|
||||
|
@ -940,12 +929,10 @@
|
|||
margin: 10px;
|
||||
margin-top: 0px;
|
||||
opacity: 0;
|
||||
|
||||
}
|
||||
|
||||
.cd-mediaitem-mvview-overlay > * {
|
||||
pointer-events: auto;
|
||||
|
||||
}
|
||||
|
||||
.cd-mediaitem-mvview > .cd-mediaitem-mvview-overlay {
|
||||
|
@ -958,15 +945,12 @@
|
|||
|
||||
.cd-mediaitem-mvview + .cd-mediaitem-mvview-overlay {
|
||||
pointer-events: none;
|
||||
|
||||
}
|
||||
|
||||
.cd-mediaitem-mvview:hover + .cd-mediaitem-mvview-overlay {
|
||||
opacity: 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.cd-mediaitem-mvview .artwork.round {
|
||||
border-radius: var(--mediaItemRadiusRound);
|
||||
}
|
||||
|
@ -982,10 +966,9 @@
|
|||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
/* mediaitem-square */
|
||||
.cd-mediaitem-square {
|
||||
--transitionDuration: .5s;
|
||||
--transitionDuration: 0.5s;
|
||||
--scaleRate: 1.25;
|
||||
--scaleRateArtwork: 1;
|
||||
width: calc(160px * var(--windowRelativeScale));
|
||||
|
@ -1062,7 +1045,6 @@
|
|||
bottom: 14px;
|
||||
left: 14px;
|
||||
z-index: 2;
|
||||
|
||||
}
|
||||
|
||||
> .menu-btn {
|
||||
|
@ -1110,7 +1092,6 @@
|
|||
// }
|
||||
// }
|
||||
|
||||
|
||||
.info-rect {
|
||||
width: 90%;
|
||||
height: 100%;
|
||||
|
@ -1119,7 +1100,6 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
@ -1174,19 +1154,19 @@
|
|||
--scaleRateArtwork: 1.1;
|
||||
width: calc(240px * var(--scaleRate));
|
||||
height: calc(200px * var(--scaleRate));
|
||||
|
||||
|
||||
.artwork-container > .artwork {
|
||||
width: calc(220px * var(--scaleRateArtwork));
|
||||
height: calc(123px * var(--scaleRateArtwork));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: 1550px) {
|
||||
--scaleRate: 1.25;
|
||||
--scaleRateArtwork: 1.25;
|
||||
width: calc(240px * var(--scaleRate));
|
||||
height: calc(200px * var(--scaleRate));
|
||||
|
||||
|
||||
.artwork-container > .artwork {
|
||||
width: calc(220px * var(--scaleRateArtwork));
|
||||
height: calc(123px * var(--scaleRateArtwork));
|
||||
|
@ -1212,19 +1192,19 @@
|
|||
--scaleRateArtwork: 1.1;
|
||||
width: calc(240px * var(--scaleRate));
|
||||
height: calc(200px * var(--scaleRate));
|
||||
|
||||
|
||||
.artwork-container > .artwork {
|
||||
width: calc(220px * var(--scaleRateArtwork));
|
||||
height: calc(123px * var(--scaleRateArtwork));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: 1550px) {
|
||||
--scaleRate: 1.25;
|
||||
--scaleRateArtwork: 1.25;
|
||||
width: calc(240px * var(--scaleRate));
|
||||
height: calc(200px * var(--scaleRate));
|
||||
|
||||
|
||||
.artwork-container > .artwork {
|
||||
width: calc(220px * var(--scaleRateArtwork));
|
||||
height: calc(123px * var(--scaleRateArtwork));
|
||||
|
@ -1338,7 +1318,7 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
.artwork{
|
||||
.artwork {
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
.info-rect-card::before {
|
||||
|
@ -1430,7 +1410,6 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
> .play-btn,
|
||||
> .menu-btn {
|
||||
opacity: 1;
|
||||
|
@ -1438,7 +1417,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.title {
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
|
@ -1471,7 +1449,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.listitem-horizontal {
|
||||
.cd-mediaitem-list-item {
|
||||
width: 350px;
|
||||
|
@ -1497,7 +1474,6 @@
|
|||
|
||||
&:hover::-webkit-scrollbar {
|
||||
display: initial;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1536,9 +1512,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/* Switch Checkbox */
|
||||
input[type=checkbox][switch] {
|
||||
input[type="checkbox"][switch] {
|
||||
width: 38px;
|
||||
appearance: none;
|
||||
border-radius: 32px;
|
||||
|
@ -1554,12 +1529,12 @@ input[type=checkbox][switch] {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
input[type=checkbox][switch]:focus,
|
||||
input[type=checkbox][switch]:active {
|
||||
input[type="checkbox"][switch]:focus,
|
||||
input[type="checkbox"][switch]:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type=checkbox][switch]:checked {
|
||||
input[type="checkbox"][switch]:checked {
|
||||
background: var(--keyColor);
|
||||
border: 0 solid var(--keyColor);
|
||||
mix-blend-mode: unset;
|
||||
|
@ -1573,43 +1548,41 @@ input[type=checkbox][switch]:checked {
|
|||
}
|
||||
}
|
||||
|
||||
input[type=checkbox][switch]::before {
|
||||
input[type="checkbox"][switch]::before {
|
||||
background: white;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
position: absolute;
|
||||
content: ' ';
|
||||
content: " ";
|
||||
border-radius: 32px;
|
||||
transition: .10s left var(--appleEase);
|
||||
transform: scale(.75);
|
||||
transition: 0.1s left var(--appleEase);
|
||||
transform: scale(0.75);
|
||||
}
|
||||
|
||||
|
||||
input[type=checkbox][switch]:checked::before {
|
||||
input[type="checkbox"][switch]:checked::before {
|
||||
background: white;
|
||||
top: -1px;
|
||||
left: 13px;
|
||||
transition: .10s left var(--appleEase);
|
||||
transform: scale(.75);
|
||||
transition: 0.1s left var(--appleEase);
|
||||
transform: scale(0.75);
|
||||
}
|
||||
|
||||
input[type=checkbox][switch]:disabled::before {
|
||||
opacity: .5;
|
||||
input[type="checkbox"][switch]:disabled::before {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
input[type=checkbox][switch]:active::before {
|
||||
input[type="checkbox"][switch]:active::before {
|
||||
left: 13px;
|
||||
}
|
||||
|
||||
input[type=checkbox][switch]:checked:active::before {
|
||||
input[type="checkbox"][switch]:checked:active::before {
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
/* End Switch Checkbox */
|
||||
|
||||
|
||||
.header-text {
|
||||
margin: 0px;
|
||||
}
|
||||
|
@ -1649,7 +1622,7 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
|
||||
.media-item--small .text {
|
||||
font-weight: 600;
|
||||
font-size: 0.90em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.media-item--small .subtext {
|
||||
|
@ -1684,11 +1657,11 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
background-repeat: no-repeat;
|
||||
border-radius: 8px;
|
||||
box-shadow: inset 0px 0px 0px 1px rgb(200 200 200 / 16%), 0 8px 40px rgb(0 0 0 / 0.55);
|
||||
transition: transform .10s var(--appleEase);
|
||||
transition: transform 0.1s var(--appleEase);
|
||||
}
|
||||
|
||||
.media-artwork.paused {
|
||||
transition: transform .35s var(--appleEase);
|
||||
transition: transform 0.35s var(--appleEase);
|
||||
transform: scale(0.85);
|
||||
}
|
||||
|
||||
|
@ -1727,7 +1700,7 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
background-size: 12px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
opacity: 0.70;
|
||||
opacity: 0.7;
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
|
||||
|
@ -1745,7 +1718,7 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
z-index: -1;
|
||||
transform: scale(0.5);
|
||||
pointer-events: none;
|
||||
transition: opacity .10s var(--appleEase), transform .10s var(--appleEase);
|
||||
transition: opacity 0.1s var(--appleEase), transform 0.1s var(--appleEase);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
@ -1768,7 +1741,7 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
height: 32px;
|
||||
border: 0px;
|
||||
box-shadow: unset;
|
||||
opacity: 0.70;
|
||||
opacity: 0.7;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
|
@ -1785,7 +1758,7 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
z-index: -1;
|
||||
transform: scale(0.5);
|
||||
pointer-events: none;
|
||||
transition: opacity .10s var(--appleEase), transform .10s var(--appleEase);
|
||||
transition: opacity 0.1s var(--appleEase), transform 0.1s var(--appleEase);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
@ -1845,7 +1818,7 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
}
|
||||
|
||||
.playback-button.stop {
|
||||
background-image: url('./assets/cider-icons/stop.svg');
|
||||
background-image: url("./assets/cider-icons/stop.svg");
|
||||
background-size: 38px;
|
||||
background-position: center;
|
||||
}
|
||||
|
@ -1863,25 +1836,25 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
}
|
||||
|
||||
.playback-button.pause {
|
||||
background-image: url('./assets/cider-icons/pause.svg');
|
||||
background-image: url("./assets/cider-icons/pause.svg");
|
||||
background-size: 38px;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.playback-button.play {
|
||||
background-image: url('./assets/cider-icons/play.svg');
|
||||
background-image: url("./assets/cider-icons/play.svg");
|
||||
background-size: 38px;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.playback-button.next {
|
||||
background-image: url('./assets/cider-icons/forward.svg');
|
||||
background-image: url("./assets/cider-icons/forward.svg");
|
||||
background-size: 60%;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.playback-button.previous {
|
||||
background-image: url('./assets/cider-icons/backward.svg');
|
||||
background-image: url("./assets/cider-icons/backward.svg");
|
||||
background-size: 60%;
|
||||
background-position: center;
|
||||
}
|
||||
|
@ -1950,7 +1923,7 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
}
|
||||
|
||||
.player-song-artist {
|
||||
font-size: 1.0em;
|
||||
font-size: 1em;
|
||||
text-align: left;
|
||||
margin: 0 auto;
|
||||
color: var(--keyColor);
|
||||
|
@ -1999,10 +1972,8 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.list-entry-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -2204,9 +2175,9 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
.fancy-pills {
|
||||
.nav-pills {
|
||||
position: relative;
|
||||
|
||||
|
||||
.nav-link {
|
||||
transition: transform .3s var(--appleEase);
|
||||
transition: transform 0.3s var(--appleEase);
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
|
@ -2215,8 +2186,7 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
-webkit-user-drag: none;
|
||||
font-weight: 500;
|
||||
margin: 0px 4px;
|
||||
|
||||
|
||||
|
||||
&:after {
|
||||
--dist: 1px;
|
||||
content: "";
|
||||
|
@ -2231,27 +2201,23 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
border-radius: 50px;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transition: background-color .5s var(--appleEase), opacity 0.25s var(--appleEase), border-radius .32s var(--appleEase);
|
||||
transition: background-color 0.5s var(--appleEase), opacity 0.25s var(--appleEase), border-radius 0.32s var(--appleEase);
|
||||
}
|
||||
|
||||
|
||||
|
||||
&:hover {
|
||||
outline: none;
|
||||
transform: scale(1.1);
|
||||
// background: #eee;
|
||||
background: transparent;
|
||||
color: #333;
|
||||
|
||||
|
||||
&:after {
|
||||
opacity: 1;
|
||||
background-color: #eee;
|
||||
transition: background-color .25s var(--appleEase),
|
||||
border-radius .25s var(--appleEase),
|
||||
color .0s var(--appleEase),
|
||||
opacity 0.0s var(--appleEase);
|
||||
transition: background-color 0.25s var(--appleEase), border-radius 0.25s var(--appleEase), color 0s var(--appleEase), opacity 0s var(--appleEase);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.active {
|
||||
outline: none;
|
||||
transform: scale(1.1);
|
||||
|
@ -2259,24 +2225,22 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
background: transparent;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
|
||||
|
||||
&:after {
|
||||
opacity: 1;
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
.nav-link.active {
|
||||
outline: none;
|
||||
transform: scale(1.0);
|
||||
transform: scale(1);
|
||||
background: transparent;
|
||||
color: #eee;
|
||||
transform: scale(1.0);
|
||||
|
||||
transform: scale(1);
|
||||
|
||||
&:after {
|
||||
background: rgb(200 200 200 / 15%);
|
||||
opacity: 1;
|
||||
|
@ -2284,12 +2248,12 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
// border-radius: 5px;
|
||||
--dist: 4px;
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
z-index: 1;
|
||||
color: #333;
|
||||
|
||||
|
||||
&:after {
|
||||
background: #eee;
|
||||
border-radius: inherit;
|
||||
|
@ -2298,9 +2262,9 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -2387,7 +2351,7 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
border-radius: 50%;
|
||||
div {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
@ -2399,8 +2363,7 @@ input[type=checkbox][switch]:checked:active::before {
|
|||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 16px;
|
||||
.md-input-number{
|
||||
.md-input-number {
|
||||
min-width: 12em;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,14 +22,13 @@
|
|||
--chromeHeight1: 70px;
|
||||
|
||||
.app-content-container {
|
||||
width:100%;
|
||||
height:100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
#app-content {
|
||||
width:100%;
|
||||
height:100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.fs-search {
|
||||
|
||||
.search-input--icon {
|
||||
width: 4em;
|
||||
background-size: 40%;
|
||||
|
@ -38,7 +37,7 @@
|
|||
input {
|
||||
padding-left: 2em;
|
||||
font-size: 2em;
|
||||
border-radius: var(--mediaItemRadius)
|
||||
border-radius: var(--mediaItemRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,42 +55,41 @@
|
|||
z-index: 9999;
|
||||
|
||||
.top-nav-group {
|
||||
background : #1e1e1e99;
|
||||
border : 1px solid lighten(@baseColor, 8);
|
||||
background: #1e1e1e99;
|
||||
border: 1px solid lighten(@baseColor, 8);
|
||||
border-radius: 12px;
|
||||
display : flex;
|
||||
height : 55px;
|
||||
display: flex;
|
||||
height: 55px;
|
||||
width: 90%;
|
||||
backdrop-filter: var(--glassFilter);
|
||||
|
||||
|
||||
.app-sidebar-item {
|
||||
background-color: #1e1e1e00;
|
||||
border-radius : 10px !important;
|
||||
border : 0px;
|
||||
min-width : 120px;
|
||||
padding : 6px;
|
||||
justify-content : center;
|
||||
align-items : center;
|
||||
margin : 0px;
|
||||
height : 100%;
|
||||
position : relative;
|
||||
border-radius: 10px !important;
|
||||
border: 0px;
|
||||
min-width: 120px;
|
||||
padding: 6px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
font-size: 1.1em;
|
||||
font-weight: 500;
|
||||
|
||||
&:before {
|
||||
--dist : 1px;
|
||||
content : '';
|
||||
position : absolute;
|
||||
top : var(--dist);
|
||||
left : var(--dist);
|
||||
right : var(--dist);
|
||||
bottom : var(--dist);
|
||||
--dist: 1px;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: var(--dist);
|
||||
left: var(--dist);
|
||||
right: var(--dist);
|
||||
bottom: var(--dist);
|
||||
background-color: #fff;
|
||||
opacity : 0;
|
||||
border-radius : 10px;
|
||||
transform : scale(0.5);
|
||||
transition : transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||
opacity: 0;
|
||||
border-radius: 10px;
|
||||
transform: scale(0.5);
|
||||
transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
&:after {
|
||||
|
@ -103,8 +101,8 @@
|
|||
|
||||
&:before {
|
||||
transition: transform 0.1s ease-in-out, opacity 0.1s ease-in-out;
|
||||
opacity : .1;
|
||||
transform : scale(1);
|
||||
opacity: 0.1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,15 +110,15 @@
|
|||
background-color: transparent;
|
||||
|
||||
&:before {
|
||||
opacity : .2;
|
||||
opacity: 0.2;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
&.md-btn-primary {
|
||||
box-shadow : 0px 0px 0px 1px lighten(@baseColor, @colorMixRate * 8);
|
||||
box-shadow: 0px 0px 0px 1px lighten(@baseColor, @colorMixRate * 8);
|
||||
background-color: lighten(@baseColor, @colorMixRate * 5);
|
||||
z-index : 1;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,7 +162,7 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.volume-button--small {
|
||||
|
@ -178,7 +176,7 @@
|
|||
width: 30px;
|
||||
border: 0px;
|
||||
box-shadow: unset;
|
||||
opacity: 0.70;
|
||||
opacity: 0.7;
|
||||
background-image: url("./assets/feather/volume-2.svg");
|
||||
}
|
||||
|
||||
|
@ -190,7 +188,7 @@
|
|||
background-image: url("./assets/feather/volume.svg");
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
|
@ -228,7 +226,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
background-size: cover;
|
||||
|
@ -247,13 +244,10 @@
|
|||
.bg-artwork-container .bg-artwork {
|
||||
filter: brightness(85%) saturate(95%) blur(180px) contrast(0.9) opacity(0.9);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.lyrics-col {
|
||||
|
||||
height: 62vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -278,11 +272,9 @@
|
|||
.lyric-line {
|
||||
font-size: 35px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.queue-col {
|
||||
|
||||
width: 60vh;
|
||||
height: 62vh;
|
||||
|
||||
|
@ -361,7 +353,8 @@
|
|||
}
|
||||
|
||||
.app-playback-controls {
|
||||
.song-artist, .song-name {
|
||||
.song-artist,
|
||||
.song-name {
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
|
@ -414,8 +407,6 @@
|
|||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.app-playback-controls .song-progress {
|
||||
|
@ -436,7 +427,7 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
> input[type=range] {
|
||||
> input[type="range"] {
|
||||
&::-webkit-slider-thumb {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
|
@ -445,7 +436,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
input[type="range"] {
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
|
@ -462,7 +453,7 @@
|
|||
border-radius: 100%;
|
||||
background: var(--songProgressColor);
|
||||
cursor: default;
|
||||
transition: opacity .10s var(--appleEase), transform .10s var(--appleEase);
|
||||
transition: opacity 0.1s var(--appleEase), transform 0.1s var(--appleEase);
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
|
@ -482,7 +473,6 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.cd-mediaitem-square {
|
||||
|
@ -556,18 +546,18 @@
|
|||
.playlist-page .playlist-display {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
flex:1;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
|
||||
.playlistInfo {
|
||||
>.row {
|
||||
> .row {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-controls {
|
||||
div {
|
||||
width:100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -156,8 +156,7 @@
|
|||
}
|
||||
|
||||
.close-btn {
|
||||
.menu-panel.menu-header-text.close-btn
|
||||
|
||||
.menu-panel.menu-header-text.close-btn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +179,7 @@
|
|||
}
|
||||
|
||||
.close-btn {
|
||||
.menu-panel.menu-header-text.close-btn
|
||||
.menu-panel.menu-header-text.close-btn;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -294,7 +293,6 @@
|
|||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
|
||||
|
||||
.menu-option {
|
||||
text-align: left;
|
||||
display: flex;
|
||||
|
@ -323,7 +321,7 @@
|
|||
opacity: 0;
|
||||
transform: scale(0.98);
|
||||
z-index: -1;
|
||||
transition: transform .25s ease-out, opacity .25s ease-out;
|
||||
transition: transform 0.25s ease-out, opacity 0.25s ease-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
@ -336,7 +334,7 @@
|
|||
|
||||
&:active {
|
||||
&::before {
|
||||
transition: transform .1s ease-in-out, opacity .1s ease-in-out;
|
||||
transition: transform 0.1s ease-in-out, opacity 0.1s ease-in-out;
|
||||
opacity: 1;
|
||||
transform: scale(0.98);
|
||||
background: var(--selected-click);
|
||||
|
@ -372,7 +370,7 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(196, 43, 28)
|
||||
background-color: rgb(196, 43, 28);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -430,7 +428,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.moreinfo-modal {
|
||||
.modal-window {
|
||||
height: 70%;
|
||||
|
@ -496,7 +493,8 @@
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
.song-artist, .song-album {
|
||||
.song-artist,
|
||||
.song-album {
|
||||
opacity: 0.75;
|
||||
cursor: pointer;
|
||||
|
||||
|
@ -521,4 +519,4 @@
|
|||
&.svg-md {
|
||||
--size: 1.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,59 +1,59 @@
|
|||
// Linux
|
||||
body[platform="linux"] {
|
||||
#window-controls-container {
|
||||
//display: none;
|
||||
}
|
||||
#window-controls-container {
|
||||
//display: none;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
justify-content: flex-end;
|
||||
align-items : center;
|
||||
padding-right : 6px;
|
||||
.window-controls {
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding-right: 6px;
|
||||
|
||||
>div {
|
||||
--iconSize: 16px;
|
||||
> div {
|
||||
--iconSize: 16px;
|
||||
|
||||
&.close,
|
||||
&.minmax,
|
||||
&.minimize,
|
||||
&.minmax.restore {
|
||||
background-image: unset!important;
|
||||
position : relative;
|
||||
display : grid;
|
||||
align-content : center;
|
||||
text-align : center;
|
||||
height : 36px!important;
|
||||
width : 36px!important;
|
||||
border-radius : 50px;
|
||||
transition: background-color .1s ease-in-out;
|
||||
&.close,
|
||||
&.minmax,
|
||||
&.minimize,
|
||||
&.minmax.restore {
|
||||
background-image: unset !important;
|
||||
position: relative;
|
||||
display: grid;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
height: 36px !important;
|
||||
width: 36px !important;
|
||||
border-radius: 50px;
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background: rgb(200 200 200 / 10%)!important;
|
||||
}
|
||||
}
|
||||
|
||||
&.close::before {
|
||||
font-family: "codicon";
|
||||
font-size : var(--iconSize);
|
||||
content : "";
|
||||
}
|
||||
|
||||
&.minmax::before {
|
||||
font-family: "codicon";
|
||||
font-size : var(--iconSize);
|
||||
content : "";
|
||||
}
|
||||
|
||||
&.minimize::before {
|
||||
font-family: "codicon";
|
||||
font-size : var(--iconSize);
|
||||
content : "";
|
||||
}
|
||||
|
||||
&.restore::before {
|
||||
font-family: "codicon";
|
||||
font-size : var(--iconSize);
|
||||
content : "";
|
||||
}
|
||||
&:hover {
|
||||
background: rgb(200 200 200 / 10%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.close::before {
|
||||
font-family: "codicon";
|
||||
font-size: var(--iconSize);
|
||||
content: "";
|
||||
}
|
||||
|
||||
&.minmax::before {
|
||||
font-family: "codicon";
|
||||
font-size: var(--iconSize);
|
||||
content: "";
|
||||
}
|
||||
|
||||
&.minimize::before {
|
||||
font-family: "codicon";
|
||||
font-size: var(--iconSize);
|
||||
content: "";
|
||||
}
|
||||
|
||||
&.restore::before {
|
||||
font-family: "codicon";
|
||||
font-size: var(--iconSize);
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,79 +1,79 @@
|
|||
body[platform="darwin"] {
|
||||
html {
|
||||
background: transparent !important;
|
||||
html {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
&.notransparency::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#app {
|
||||
&.simplebg {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&.notransparency::before {
|
||||
display: none;
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#app {
|
||||
&.simplebg {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-chrome {
|
||||
background-color: var(--macOSChromeColor);
|
||||
}
|
||||
|
||||
&.twopanel {
|
||||
--chromeHeight1: 55px;
|
||||
--chromeHeight: calc(var(--chromeHeight1) + var(--chromeHeight2));
|
||||
|
||||
.app-chrome .app-chrome-item.search {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.app-chrome .app-mainmenu {
|
||||
width: 46px;
|
||||
}
|
||||
|
||||
.app-chrome.chrome-bottom {
|
||||
background-color: var(--macOSChromeColor);
|
||||
}
|
||||
}
|
||||
|
||||
&[window-state="normal"] {
|
||||
&::after {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
box-shadow: inset 0px 0px .5px 1px rgb(200 200 200 / 40%);
|
||||
border-radius: 10px;
|
||||
content: " ";
|
||||
z-index: 999999;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.app-chrome {
|
||||
background-color: var(--macOSChromeColor);
|
||||
}
|
||||
|
||||
#app-main {
|
||||
background-color: transparent;
|
||||
&.twopanel {
|
||||
--chromeHeight1: 55px;
|
||||
--chromeHeight: calc(var(--chromeHeight1) + var(--chromeHeight2));
|
||||
|
||||
.app-navigation {
|
||||
background: transparent;
|
||||
}
|
||||
.app-chrome .app-chrome-item.search {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
#app-content {
|
||||
background-color: #1e1e1e6b;
|
||||
}
|
||||
.app-chrome .app-mainmenu {
|
||||
width: 46px;
|
||||
}
|
||||
|
||||
.app-chrome.chrome-bottom {
|
||||
background-color: var(--macOSChromeColor);
|
||||
}
|
||||
}
|
||||
|
||||
.settings-window.maxed {
|
||||
.tabs>.col-auto {
|
||||
transition: padding-top .3s linear;
|
||||
padding-top: var(--chromeHeight1);
|
||||
}
|
||||
&[window-state="normal"] {
|
||||
&::after {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
box-shadow: inset 0px 0px 0.5px 1px rgb(200 200 200 / 40%);
|
||||
border-radius: 10px;
|
||||
content: " ";
|
||||
z-index: 999999;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#app-main {
|
||||
background-color: transparent;
|
||||
|
||||
.app-navigation {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#apple-music-video-player-controls #player-exit {
|
||||
margin-top: 18px;
|
||||
left: 70px;
|
||||
#app-content {
|
||||
background-color: #1e1e1e6b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings-window.maxed {
|
||||
.tabs > .col-auto {
|
||||
transition: padding-top 0.3s linear;
|
||||
padding-top: var(--chromeHeight1);
|
||||
}
|
||||
}
|
||||
|
||||
#apple-music-video-player-controls #player-exit {
|
||||
margin-top: 18px;
|
||||
left: 70px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
#app.macosemu {
|
||||
|
||||
|
||||
|
||||
.app-chrome .app-chrome-item > .window-controls-macos {
|
||||
@controlSize: 12px;
|
||||
display: flex;
|
||||
|
@ -42,8 +39,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.usermenu-body{
|
||||
.usermenu-body {
|
||||
left: calc(100vw - 260px);
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.volume-button--small {
|
||||
|
@ -79,7 +79,7 @@
|
|||
width: 30px;
|
||||
border: 0px;
|
||||
box-shadow: unset;
|
||||
opacity: 0.70;
|
||||
opacity: 0.7;
|
||||
background-image: url("./assets/feather/volume-2.svg");
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@
|
|||
background-image: url("./assets/feather/volume.svg");
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
|
@ -129,7 +129,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
background-size: cover;
|
||||
|
@ -157,9 +156,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.lyrics-col {
|
||||
|
||||
height: 62vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -184,11 +181,9 @@
|
|||
.lyric-line {
|
||||
font-size: 35px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.queue-col {
|
||||
|
||||
width: 60vh;
|
||||
height: 50vh;
|
||||
|
||||
|
@ -281,11 +276,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.app-playback-controls {
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
.song-artist, .song-name {
|
||||
.song-artist,
|
||||
.song-name {
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
|
@ -338,8 +333,6 @@
|
|||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.app-playback-controls .song-progress {
|
||||
|
@ -360,7 +353,7 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
> input[type=range] {
|
||||
> input[type="range"] {
|
||||
&::-webkit-slider-thumb {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
|
@ -369,7 +362,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
input[type="range"] {
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
|
@ -386,7 +379,7 @@
|
|||
border-radius: 100%;
|
||||
background: var(--songProgressColor);
|
||||
cursor: default;
|
||||
transition: opacity .10s var(--appleEase), transform .10s var(--appleEase);
|
||||
transition: opacity 0.1s var(--appleEase), transform 0.1s var(--appleEase);
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
|
@ -405,6 +398,5 @@
|
|||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,370 +1,370 @@
|
|||
@-webkit-keyframes notyf-fadeinup {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(25%)
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(25%);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0)
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes notyf-fadeinup {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(25%)
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(25%);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0)
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes notyf-fadeinleft {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(25%)
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(25%);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0)
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes notyf-fadeinleft {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(25%)
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(25%);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0)
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes notyf-fadeoutright {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateX(0)
|
||||
}
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateX(25%)
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateX(25%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes notyf-fadeoutright {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateX(0)
|
||||
}
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateX(25%)
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateX(25%);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes notyf-fadeoutdown {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0)
|
||||
}
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(25%)
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(25%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes notyf-fadeoutdown {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0)
|
||||
}
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(25%)
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(25%);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes ripple {
|
||||
0% {
|
||||
transform: scale(0) translateY(-45%) translateX(13%)
|
||||
}
|
||||
0% {
|
||||
transform: scale(0) translateY(-45%) translateX(13%);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scale(1) translateY(-45%) translateX(13%)
|
||||
}
|
||||
to {
|
||||
transform: scale(1) translateY(-45%) translateX(13%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ripple {
|
||||
0% {
|
||||
transform: scale(0) translateY(-45%) translateX(13%)
|
||||
}
|
||||
0% {
|
||||
transform: scale(0) translateY(-45%) translateX(13%);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scale(1) translateY(-45%) translateX(13%)
|
||||
}
|
||||
to {
|
||||
transform: scale(1) translateY(-45%) translateX(13%);
|
||||
}
|
||||
}
|
||||
|
||||
.notyf {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
pointer-events: none;
|
||||
box-sizing: border-box;
|
||||
padding: 20px
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
pointer-events: none;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.notyf__icon--error,
|
||||
.notyf__icon--success {
|
||||
height: 21px;
|
||||
width: 21px;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
position: relative
|
||||
height: 21px;
|
||||
width: 21px;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notyf__icon--error:after,
|
||||
.notyf__icon--error:before {
|
||||
content: "";
|
||||
background: currentColor;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
border-radius: 3px;
|
||||
left: 9px;
|
||||
height: 12px;
|
||||
top: 5px
|
||||
content: "";
|
||||
background: currentColor;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
border-radius: 3px;
|
||||
left: 9px;
|
||||
height: 12px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.notyf__icon--error:after {
|
||||
transform: rotate(-45deg)
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.notyf__icon--error:before {
|
||||
transform: rotate(45deg)
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.notyf__icon--success:after,
|
||||
.notyf__icon--success:before {
|
||||
content: "";
|
||||
background: currentColor;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
border-radius: 3px
|
||||
content: "";
|
||||
background: currentColor;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.notyf__icon--success:after {
|
||||
height: 6px;
|
||||
transform: rotate(-45deg);
|
||||
top: 9px;
|
||||
left: 6px
|
||||
height: 6px;
|
||||
transform: rotate(-45deg);
|
||||
top: 9px;
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
.notyf__icon--success:before {
|
||||
height: 11px;
|
||||
transform: rotate(45deg);
|
||||
top: 5px;
|
||||
left: 10px
|
||||
height: 11px;
|
||||
transform: rotate(45deg);
|
||||
top: 5px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.notyf__toast {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
pointer-events: auto;
|
||||
-webkit-animation: notyf-fadeinup .3s ease-in forwards;
|
||||
animation: notyf-fadeinup .3s ease-in forwards;
|
||||
box-shadow: 0 3px 7px 0 rgba(0, 0, 0, .25);
|
||||
position: relative;
|
||||
padding: 0 15px;
|
||||
border-radius: 2px;
|
||||
max-width: 300px;
|
||||
transform: translateY(25%);
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
pointer-events: auto;
|
||||
-webkit-animation: notyf-fadeinup 0.3s ease-in forwards;
|
||||
animation: notyf-fadeinup 0.3s ease-in forwards;
|
||||
box-shadow: 0 3px 7px 0 rgba(0, 0, 0, 0.25);
|
||||
position: relative;
|
||||
padding: 0 15px;
|
||||
border-radius: 2px;
|
||||
max-width: 300px;
|
||||
transform: translateY(25%);
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notyf__toast--disappear {
|
||||
transform: translateY(0);
|
||||
-webkit-animation: notyf-fadeoutdown .3s forwards;
|
||||
animation: notyf-fadeoutdown .3s forwards;
|
||||
-webkit-animation-delay: .25s;
|
||||
animation-delay: .25s
|
||||
transform: translateY(0);
|
||||
-webkit-animation: notyf-fadeoutdown 0.3s forwards;
|
||||
animation: notyf-fadeoutdown 0.3s forwards;
|
||||
-webkit-animation-delay: 0.25s;
|
||||
animation-delay: 0.25s;
|
||||
}
|
||||
|
||||
.notyf__toast--disappear .notyf__icon,
|
||||
.notyf__toast--disappear .notyf__message {
|
||||
-webkit-animation: notyf-fadeoutdown .3s forwards;
|
||||
animation: notyf-fadeoutdown .3s forwards;
|
||||
opacity: 1;
|
||||
transform: translateY(0)
|
||||
-webkit-animation: notyf-fadeoutdown 0.3s forwards;
|
||||
animation: notyf-fadeoutdown 0.3s forwards;
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.notyf__toast--disappear .notyf__dismiss {
|
||||
-webkit-animation: notyf-fadeoutright .3s forwards;
|
||||
animation: notyf-fadeoutright .3s forwards;
|
||||
opacity: 1;
|
||||
transform: translateX(0)
|
||||
-webkit-animation: notyf-fadeoutright 0.3s forwards;
|
||||
animation: notyf-fadeoutright 0.3s forwards;
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.notyf__toast--disappear .notyf__message {
|
||||
-webkit-animation-delay: .05s;
|
||||
animation-delay: .05s
|
||||
-webkit-animation-delay: 0.05s;
|
||||
animation-delay: 0.05s;
|
||||
}
|
||||
|
||||
.notyf__toast--upper {
|
||||
margin-bottom: 20px
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.notyf__toast--lower {
|
||||
margin-top: 20px
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.notyf__toast--dismissible .notyf__wrapper {
|
||||
padding-right: 30px
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.notyf__ripple {
|
||||
height: 400px;
|
||||
width: 400px;
|
||||
position: absolute;
|
||||
transform-origin: bottom right;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border-radius: 50%;
|
||||
transform: scale(0) translateY(-51%) translateX(13%);
|
||||
z-index: 5;
|
||||
-webkit-animation: ripple .4s ease-out forwards;
|
||||
animation: ripple .4s ease-out forwards
|
||||
height: 400px;
|
||||
width: 400px;
|
||||
position: absolute;
|
||||
transform-origin: bottom right;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border-radius: 50%;
|
||||
transform: scale(0) translateY(-51%) translateX(13%);
|
||||
z-index: 5;
|
||||
-webkit-animation: ripple 0.4s ease-out forwards;
|
||||
animation: ripple 0.4s ease-out forwards;
|
||||
}
|
||||
|
||||
.notyf__wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 17px;
|
||||
padding-bottom: 17px;
|
||||
padding-right: 15px;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
z-index: 10
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 17px;
|
||||
padding-bottom: 17px;
|
||||
padding-right: 15px;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.notyf__icon {
|
||||
width: 22px;
|
||||
text-align: center;
|
||||
font-size: 1.3em;
|
||||
opacity: 0;
|
||||
-webkit-animation: notyf-fadeinup .3s forwards;
|
||||
animation: notyf-fadeinup .3s forwards;
|
||||
-webkit-animation-delay: .3s;
|
||||
animation-delay: .3s;
|
||||
margin-right: 13px
|
||||
width: 22px;
|
||||
text-align: center;
|
||||
font-size: 1.3em;
|
||||
opacity: 0;
|
||||
-webkit-animation: notyf-fadeinup 0.3s forwards;
|
||||
animation: notyf-fadeinup 0.3s forwards;
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
margin-right: 13px;
|
||||
}
|
||||
|
||||
.notyf__dismiss {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 26px;
|
||||
margin-right: -15px;
|
||||
-webkit-animation: notyf-fadeinleft .3s forwards;
|
||||
animation: notyf-fadeinleft .3s forwards;
|
||||
-webkit-animation-delay: .35s;
|
||||
animation-delay: .35s;
|
||||
opacity: 0
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 26px;
|
||||
margin-right: -15px;
|
||||
-webkit-animation: notyf-fadeinleft 0.3s forwards;
|
||||
animation: notyf-fadeinleft 0.3s forwards;
|
||||
-webkit-animation-delay: 0.35s;
|
||||
animation-delay: 0.35s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.notyf__dismiss-btn {
|
||||
background-color: rgba(0, 0, 0, .25);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: opacity .2s ease, background-color .2s ease;
|
||||
outline: none;
|
||||
opacity: .35;
|
||||
height: 100%;
|
||||
width: 100%
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease, background-color 0.2s ease;
|
||||
outline: none;
|
||||
opacity: 0.35;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.notyf__dismiss-btn:after,
|
||||
.notyf__dismiss-btn:before {
|
||||
content: "";
|
||||
background: #fff;
|
||||
height: 12px;
|
||||
width: 2px;
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
left: calc(50% - 1px);
|
||||
top: calc(50% - 5px)
|
||||
content: "";
|
||||
background: #fff;
|
||||
height: 12px;
|
||||
width: 2px;
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
left: calc(50% - 1px);
|
||||
top: calc(50% - 5px);
|
||||
}
|
||||
|
||||
.notyf__dismiss-btn:after {
|
||||
transform: rotate(-45deg)
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.notyf__dismiss-btn:before {
|
||||
transform: rotate(45deg)
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.notyf__dismiss-btn:hover {
|
||||
opacity: .7;
|
||||
background-color: rgba(0, 0, 0, .15)
|
||||
opacity: 0.7;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.notyf__dismiss-btn:active {
|
||||
opacity: .8
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.notyf__message {
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
-webkit-animation: notyf-fadeinup .3s forwards;
|
||||
animation: notyf-fadeinup .3s forwards;
|
||||
-webkit-animation-delay: .25s;
|
||||
animation-delay: .25s;
|
||||
line-height: 1.5em
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
-webkit-animation: notyf-fadeinup 0.3s forwards;
|
||||
animation: notyf-fadeinup 0.3s forwards;
|
||||
-webkit-animation-delay: 0.25s;
|
||||
animation-delay: 0.25s;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.notyf {
|
||||
padding: 0
|
||||
}
|
||||
@media only screen and (max-width: 480px) {
|
||||
.notyf {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.notyf__ripple {
|
||||
height: 600px;
|
||||
width: 600px;
|
||||
-webkit-animation-duration: .5s;
|
||||
animation-duration: .5s
|
||||
}
|
||||
.notyf__ripple {
|
||||
height: 600px;
|
||||
width: 600px;
|
||||
-webkit-animation-duration: 0.5s;
|
||||
animation-duration: 0.5s;
|
||||
}
|
||||
|
||||
.notyf__toast {
|
||||
max-width: none;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 -2px 7px 0 rgba(0, 0, 0, .13);
|
||||
width: 100%
|
||||
}
|
||||
.notyf__toast {
|
||||
max-width: none;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 -2px 7px 0 rgba(0, 0, 0, 0.13);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.notyf__dismiss {
|
||||
width: 56px
|
||||
}
|
||||
}
|
||||
.notyf__dismiss {
|
||||
width: 56px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1277,7 +1277,7 @@
|
|||
}
|
||||
.audiolabs-page .spprofile-line .spprofile-viewport .spprev:before,
|
||||
.audiolabs-page .spprofile-line .spprofile-viewport .nextprev:before {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -1309,7 +1309,7 @@
|
|||
background: black;
|
||||
}
|
||||
.audiolabs-page .spprofile-line .spprofile-viewport .spslide > img {
|
||||
WIDTH: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
|
|
@ -242,7 +242,6 @@
|
|||
}
|
||||
|
||||
.list-group-item {
|
||||
|
||||
&:hover {
|
||||
cursor: grab;
|
||||
}
|
||||
|
@ -294,7 +293,6 @@
|
|||
|
||||
// Search Page
|
||||
&.search-page {
|
||||
|
||||
.searchToggle {
|
||||
float: right;
|
||||
|
||||
|
@ -302,7 +300,7 @@
|
|||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
.categories{
|
||||
.categories {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
|
@ -316,7 +314,7 @@
|
|||
width: 100% !important;
|
||||
z-index: 1;
|
||||
}
|
||||
.info-rect{
|
||||
.info-rect {
|
||||
height: max-content;
|
||||
}
|
||||
.title {
|
||||
|
@ -452,7 +450,7 @@
|
|||
right: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(196, 43, 28)
|
||||
background-color: rgb(196, 43, 28);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -488,10 +486,7 @@
|
|||
display: block;
|
||||
line-break: anywhere;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Podcast Page
|
||||
|
@ -622,7 +617,7 @@
|
|||
right: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(196, 43, 28)
|
||||
background-color: rgb(196, 43, 28);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -657,10 +652,7 @@
|
|||
display: block;
|
||||
line-break: anywhere;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1230px) {
|
||||
|
@ -828,7 +820,7 @@
|
|||
margin-bottom: -10px;
|
||||
padding: 0;
|
||||
-webkit-mask-image: radial-gradient(at top left, black, transparent 70%), radial-gradient(at top right, black, transparent 70%), linear-gradient(180deg, rgb(200 200 200), transparent 98%);
|
||||
opacity: .7;
|
||||
opacity: 0.7;
|
||||
animation: playlistArtworkFadeIn 1s var(--appleEase);
|
||||
|
||||
.artworkMaterial img {
|
||||
|
@ -898,7 +890,7 @@
|
|||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: var(--heroplaceholdercolor)
|
||||
color: var(--heroplaceholdercolor);
|
||||
}
|
||||
|
||||
.nameEdit {
|
||||
|
@ -939,7 +931,7 @@
|
|||
}
|
||||
|
||||
.playlist-desc {
|
||||
transition: height .2s ease-in-out, opacity .2s ease-in-out;
|
||||
transition: height 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
flex-shrink: unset;
|
||||
|
@ -1043,8 +1035,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.friends-info {
|
||||
|
@ -1061,7 +1051,7 @@
|
|||
border-radius: 100%;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--mediaItemShadow-ShadowSubtle);
|
||||
transition: transform .2s var(--appleEase);
|
||||
transition: transform 0.2s var(--appleEase);
|
||||
margin: 6px;
|
||||
|
||||
&:hover {
|
||||
|
@ -1081,7 +1071,7 @@
|
|||
font-size: 0.9em;
|
||||
margin: 6px;
|
||||
opacity: 0.7;
|
||||
transition: height .2s ease-in-out, opacity .2s ease-in-out;
|
||||
transition: height 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||
height: 0.9em;
|
||||
}
|
||||
|
||||
|
@ -1151,13 +1141,13 @@
|
|||
}
|
||||
|
||||
.playlist-time {
|
||||
transition: height .2s ease-in-out, opacity .2s ease-in-out;
|
||||
transition: height 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||
height: 0px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.playlist-desc {
|
||||
transition: height .2s ease-in-out, opacity .2s ease-in-out;
|
||||
transition: height 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||
height: 0px !important;
|
||||
opacity: 0;
|
||||
}
|
||||
|
@ -1272,7 +1262,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.artworkContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -1282,7 +1271,7 @@
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-mask-image: radial-gradient(at top left, black, transparent 70%), radial-gradient(at top right, black, transparent 70%), linear-gradient(180deg, rgb(200 200 200), transparent 98%);
|
||||
opacity: .7;
|
||||
opacity: 0.7;
|
||||
animation: playlistArtworkFadeIn 1s var(--appleEase);
|
||||
|
||||
.artworkMaterial img {
|
||||
|
@ -1412,7 +1401,6 @@
|
|||
}
|
||||
|
||||
.artist-title {
|
||||
|
||||
.artist-play {
|
||||
transform: translateY(3px);
|
||||
margin: 14px;
|
||||
|
@ -1490,7 +1478,6 @@
|
|||
width: 90%;
|
||||
margin: 16px auto 0px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// AudioLabs page
|
||||
|
@ -1502,7 +1489,7 @@
|
|||
border-bottom: unset;
|
||||
border-top: unset;
|
||||
font-weight: 600;
|
||||
font-size: 1.0em;
|
||||
font-size: 1em;
|
||||
background: rgb(255 255 255 / 3%);
|
||||
}
|
||||
|
||||
|
@ -1548,7 +1535,7 @@
|
|||
}
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -1568,7 +1555,6 @@
|
|||
&:before {
|
||||
-webkit-mask-image: url("./views/svg/chevron-left.svg");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.nextprev {
|
||||
|
@ -1577,7 +1563,6 @@
|
|||
&:before {
|
||||
-webkit-mask-image: url("./views/svg/chevron-right.svg");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.spslide {
|
||||
|
@ -1588,7 +1573,7 @@
|
|||
background: black;
|
||||
|
||||
> img {
|
||||
WIDTH: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
@ -1646,7 +1631,6 @@
|
|||
|
||||
//Home
|
||||
.home-page {
|
||||
|
||||
.md-btn-replay {
|
||||
background-image: linear-gradient(-45deg, #2e2173, #925042);
|
||||
animation: gradient-animation 5s ease-in-out infinite;
|
||||
|
@ -1738,8 +1722,8 @@
|
|||
border-radius: var(--mediaItemRadius);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: transform .2s var(--appleEase);
|
||||
transition-delay: .1s;
|
||||
transition: transform 0.2s var(--appleEase);
|
||||
transition-delay: 0.1s;
|
||||
align-self: center;
|
||||
|
||||
&:hover {
|
||||
|
@ -1780,7 +1764,6 @@
|
|||
}
|
||||
|
||||
.top-genres-container {
|
||||
|
||||
.genre-name {
|
||||
font-size: 0.9em;
|
||||
margin: 6px 0px;
|
||||
|
@ -1810,11 +1793,11 @@
|
|||
|
||||
.cd-mediaitem-square {
|
||||
.mediaitem-artwork {
|
||||
animation: replayFadeIn .5s var(--appleEase);
|
||||
animation: replayFadeIn 0.5s var(--appleEase);
|
||||
}
|
||||
|
||||
transition: transform .2s var(--appleEase);
|
||||
transition-delay: .1s;
|
||||
transition: transform 0.2s var(--appleEase);
|
||||
transition-delay: 0.1s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
|
@ -1883,7 +1866,6 @@
|
|||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
|
||||
.oobe-header {
|
||||
font-size: 3em;
|
||||
text-shadow: var(--replayTextShadow);
|
||||
|
@ -1927,7 +1909,7 @@
|
|||
|
||||
.visualPreview {
|
||||
pointer-events: none;
|
||||
transition: .25s all;
|
||||
transition: 0.25s all;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@ -1954,9 +1936,8 @@
|
|||
outline: 4px solid var(--keyColor);
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.10) translateZ(-1px) translateY(10px);
|
||||
transform: scale(1.1) translateZ(-1px) translateY(10px);
|
||||
z-index: 1;
|
||||
box-shadow: 0px 12px 16px rgb(0 0 0 / 25%);
|
||||
}
|
||||
|
@ -1981,10 +1962,8 @@
|
|||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.oobe-titlebar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -2125,7 +2104,6 @@
|
|||
|
||||
.nav-pills {
|
||||
gap: 6px;
|
||||
|
||||
}
|
||||
|
||||
.nav-pills .nav-link {
|
||||
|
@ -2139,7 +2117,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.md-option-header {
|
||||
padding: 0px 26px;
|
||||
border-bottom: unset;
|
||||
|
@ -2192,7 +2169,7 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(196, 43, 28)
|
||||
background-color: rgb(196, 43, 28);
|
||||
}
|
||||
|
||||
&.back-btn {
|
||||
|
@ -2241,35 +2218,36 @@
|
|||
overflow-y: overlay;
|
||||
height: 100%;
|
||||
background-color: var(--panelColor2);
|
||||
padding:0px;
|
||||
padding: 0px;
|
||||
padding-top: 48px;
|
||||
border-left: 1px solid var(--borderColor);
|
||||
}
|
||||
|
||||
.github-themes-page, .installed-themes-page {
|
||||
.github-themes-page,
|
||||
.installed-themes-page {
|
||||
.header-text {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
height:100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.settings-tab-content {
|
||||
height:100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.no-sidebar {
|
||||
.gh-header {
|
||||
>.row {
|
||||
> .row {
|
||||
&:last-child {
|
||||
padding-right: 90px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tab-content {
|
||||
padding-top:0px;
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
|
@ -2277,10 +2255,10 @@
|
|||
width: 50px;
|
||||
:nth-child(2) {
|
||||
// font-size: 0px;
|
||||
opacity:0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
>.col-auto {
|
||||
> .col-auto {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
@ -2290,4 +2268,4 @@
|
|||
|
||||
#hid___BV_tab_button__ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +1,46 @@
|
|||
import { app } from "./vueapp.js"
|
||||
import {CiderCache} from './cidercache.js'
|
||||
import {CiderFrontAPI} from './ciderfrontapi.js'
|
||||
import {simulateGamepad} from './gamepad.js'
|
||||
import {CiderAudio} from '../audio/cideraudio.js'
|
||||
import {Events} from './events.js'
|
||||
import { wsapi } from "./wsapi_interop.js"
|
||||
import { MusicKitTools } from "./musickittools.js"
|
||||
import { spawnMica } from "./mica.js"
|
||||
import { svgIcon } from './components/svg-icon.js'
|
||||
import { sidebarLibraryItem } from './components/sidebar-library-item.js'
|
||||
|
||||
import { app } from "./vueapp.js";
|
||||
import { CiderCache } from "./cidercache.js";
|
||||
import { CiderFrontAPI } from "./ciderfrontapi.js";
|
||||
import { simulateGamepad } from "./gamepad.js";
|
||||
import { CiderAudio } from "../audio/cideraudio.js";
|
||||
import { Events } from "./events.js";
|
||||
import { wsapi } from "./wsapi_interop.js";
|
||||
import { MusicKitTools } from "./musickittools.js";
|
||||
import { spawnMica } from "./mica.js";
|
||||
import { svgIcon } from "./components/svg-icon.js";
|
||||
import { sidebarLibraryItem } from "./components/sidebar-library-item.js";
|
||||
|
||||
// Define window objects
|
||||
window.app = app
|
||||
window.MusicKitTools = MusicKitTools
|
||||
window.CiderAudio = CiderAudio
|
||||
window.CiderCache = CiderCache
|
||||
window.CiderFrontAPI = CiderFrontAPI
|
||||
window.wsapi = wsapi
|
||||
window.app = app;
|
||||
window.MusicKitTools = MusicKitTools;
|
||||
window.CiderAudio = CiderAudio;
|
||||
window.CiderCache = CiderCache;
|
||||
window.CiderFrontAPI = CiderFrontAPI;
|
||||
window.wsapi = wsapi;
|
||||
|
||||
if (app.cfg.advanced.disableLogging === true) {
|
||||
window.console = {
|
||||
log: function() {},
|
||||
error: function() {},
|
||||
warn: function() {},
|
||||
assert: function() {},
|
||||
debug: function() {}
|
||||
}
|
||||
window.console = {
|
||||
log: function () {},
|
||||
error: function () {},
|
||||
warn: function () {},
|
||||
assert: function () {},
|
||||
debug: function () {},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Mount Vue to #app
|
||||
app.$mount("#app")
|
||||
app.$mount("#app");
|
||||
|
||||
// Init CiderAudio and force audiocontext
|
||||
if (app.cfg.advanced.AudioContext != true) {
|
||||
app.cfg.advanced.AudioContext = true;
|
||||
window.location.reload();
|
||||
app.cfg.advanced.AudioContext = true;
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
CiderAudio.init()
|
||||
CiderAudio.init();
|
||||
|
||||
// Import gamepad support
|
||||
app.simulateGamepad = simulateGamepad
|
||||
app.spawnMica = spawnMica
|
||||
app.simulateGamepad = simulateGamepad;
|
||||
app.spawnMica = spawnMica;
|
||||
|
||||
Events.InitEvents()
|
||||
Events.InitEvents();
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
const CiderCache = {
|
||||
async getCache(file) {
|
||||
let cache = await ipcRenderer.sendSync("get-cache", file)
|
||||
if (isJson(cache)) {
|
||||
cache = JSON.parse(cache)
|
||||
if (Object.keys(cache).length === 0) {
|
||||
cache = false
|
||||
}
|
||||
} else {
|
||||
cache = false
|
||||
}
|
||||
return cache
|
||||
},
|
||||
async putCache(file, data) {
|
||||
console.log(`Caching ${file}`)
|
||||
ipcRenderer.invoke("put-cache", {
|
||||
file: file,
|
||||
data: JSON.stringify(data)
|
||||
})
|
||||
return true
|
||||
async getCache(file) {
|
||||
let cache = await ipcRenderer.sendSync("get-cache", file);
|
||||
if (isJson(cache)) {
|
||||
cache = JSON.parse(cache);
|
||||
if (Object.keys(cache).length === 0) {
|
||||
cache = false;
|
||||
}
|
||||
} else {
|
||||
cache = false;
|
||||
}
|
||||
}
|
||||
return cache;
|
||||
},
|
||||
async putCache(file, data) {
|
||||
console.log(`Caching ${file}`);
|
||||
ipcRenderer.invoke("put-cache", {
|
||||
file: file,
|
||||
data: JSON.stringify(data),
|
||||
});
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
export {CiderCache}
|
||||
export { CiderCache };
|
||||
|
|
|
@ -1,32 +1,31 @@
|
|||
const CiderFrontAPI = {
|
||||
Objects: {
|
||||
MenuEntry: function () {
|
||||
this.id = ""
|
||||
this.name = ""
|
||||
this.onClick = () => {
|
||||
}
|
||||
}
|
||||
Objects: {
|
||||
MenuEntry: function () {
|
||||
this.id = "";
|
||||
this.name = "";
|
||||
this.onClick = () => {};
|
||||
},
|
||||
AddMenuEntry(entry) {
|
||||
app.pluginMenuEntries.push(entry)
|
||||
app.pluginInstalled = true
|
||||
},
|
||||
AddMenuEntry(entry) {
|
||||
app.pluginMenuEntries.push(entry);
|
||||
app.pluginInstalled = true;
|
||||
},
|
||||
StyleSheets: {
|
||||
Add(href) {
|
||||
console.log("Adding stylesheet: " + href);
|
||||
let id = uuidv4();
|
||||
let link = document.createElement("link");
|
||||
link.rel = "stylesheet/less";
|
||||
link.type = "text/css";
|
||||
link.href = href;
|
||||
link.setAttribute("css-id", id);
|
||||
// insert the link before document.querySelector("#userTheme") in head
|
||||
document.querySelector("head").insertBefore(link, document.querySelector("#userTheme"));
|
||||
less.registerStylesheetsImmediately();
|
||||
less.refresh(true, true, true);
|
||||
return link;
|
||||
},
|
||||
StyleSheets: {
|
||||
Add(href) {
|
||||
console.log("Adding stylesheet: " + href)
|
||||
let id = uuidv4()
|
||||
let link = document.createElement("link")
|
||||
link.rel = "stylesheet/less"
|
||||
link.type = "text/css"
|
||||
link.href = href
|
||||
link.setAttribute("css-id", id)
|
||||
// insert the link before document.querySelector("#userTheme") in head
|
||||
document.querySelector("head").insertBefore(link, document.querySelector("#userTheme"))
|
||||
less.registerStylesheetsImmediately()
|
||||
less.refresh(true, true, true)
|
||||
return link
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export {CiderFrontAPI}
|
||||
export { CiderFrontAPI };
|
||||
|
|
|
@ -1,46 +1,45 @@
|
|||
import {html} from "../html.js"
|
||||
import { html } from "../html.js";
|
||||
|
||||
export const sidebarLibraryItem = Vue.component("sidebar-library-item", {
|
||||
template: html`
|
||||
<button class="app-sidebar-item"
|
||||
:class="$root.getSidebarItemClass(page)" @click="$root.setWindowHash(page)">
|
||||
<svg-icon :url="svgIconData" :name="'sidebar-' + svgIconName" v-if="svgIconData != ''"/>
|
||||
<span class="sidebar-item-text">{{ name }}</span>
|
||||
</button>
|
||||
`,
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
page: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
svgIcon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
svgIconName: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
cdClick: {
|
||||
type: Function,
|
||||
required: false,
|
||||
},
|
||||
template: html`
|
||||
<button class="app-sidebar-item" :class="$root.getSidebarItemClass(page)" @click="$root.setWindowHash(page)">
|
||||
<svg-icon :url="svgIconData" :name="'sidebar-' + svgIconName" v-if="svgIconData != ''" />
|
||||
<span class="sidebar-item-text">{{ name }}</span>
|
||||
</button>
|
||||
`,
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
app: app,
|
||||
svgIconData: "",
|
||||
};
|
||||
page: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
async mounted() {
|
||||
if (this.svgIcon) {
|
||||
this.svgIconData = this.svgIcon;
|
||||
}
|
||||
svgIcon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
methods: {},
|
||||
})
|
||||
svgIconName: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
cdClick: {
|
||||
type: Function,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
app: app,
|
||||
svgIconData: "",
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
if (this.svgIcon) {
|
||||
this.svgIconData = this.svgIcon;
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
});
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
import {html} from "../html.js"
|
||||
import { html } from "../html.js";
|
||||
|
||||
export const svgIcon = Vue.component("svg-icon", {
|
||||
template: html`
|
||||
<div class="_svg-icon" :class="classes" :svg-name="name" :style="{'--icon': 'url(' + url + ')'}"></div>
|
||||
`,
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
classes: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "./assets/repeat.svg"
|
||||
}
|
||||
}
|
||||
})
|
||||
template: html` <div class="_svg-icon" :class="classes" :svg-name="name" :style="{'--icon': 'url(' + url + ')'}"></div> `,
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
classes: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "./assets/repeat.svg",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,98 +1,94 @@
|
|||
const Events = {
|
||||
InitEvents() {
|
||||
const app = window.app
|
||||
InitEvents() {
|
||||
const app = window.app;
|
||||
|
||||
// add event listener for when window.location.hash changes
|
||||
window.addEventListener("hashchange", function () {
|
||||
app.page = "blank"
|
||||
setTimeout(()=>{
|
||||
app.appRoute(window.location.hash)
|
||||
}, 100)
|
||||
// add event listener for when window.location.hash changes
|
||||
window.addEventListener("hashchange", function () {
|
||||
app.page = "blank";
|
||||
setTimeout(() => {
|
||||
app.appRoute(window.location.hash);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
window.addEventListener("mouseup", (e) => {
|
||||
if (e.button === 3) {
|
||||
e.preventDefault();
|
||||
app.navigateBack();
|
||||
} else if (e.button === 4) {
|
||||
e.preventDefault();
|
||||
app.navigateForward();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", async function (event) {
|
||||
// CTRL + R
|
||||
if (event.keyCode === 82 && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
app.confirm(app.getLz("term.reload"), (res) => {
|
||||
if (res) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("mouseup", (e) => {
|
||||
if (e.button === 3) {
|
||||
e.preventDefault()
|
||||
app.navigateBack()
|
||||
} else if (e.button === 4) {
|
||||
e.preventDefault()
|
||||
app.navigateForward()
|
||||
}
|
||||
}
|
||||
// CTRL + SHIFT + R
|
||||
if (event.keyCode === 82 && event.ctrlKey && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
window.location.reload();
|
||||
}
|
||||
// CTRL + E
|
||||
if (event.keyCode === 69 && event.ctrlKey) {
|
||||
app.invokeDrawer("queue");
|
||||
}
|
||||
// CTRL+H
|
||||
if (event.keyCode === 72 && event.ctrlKey) {
|
||||
app.appRoute("home");
|
||||
}
|
||||
// CTRL+SHIFT+H
|
||||
if (event.ctrlKey && event.shiftKey && event.keyCode == 72) {
|
||||
let hist = await app.mk.api.v3.music(`/v1/me/recent/played/tracks`, {
|
||||
l: app.mklang,
|
||||
});
|
||||
app.showCollection(hist.data, app.getLz("term.history"));
|
||||
}
|
||||
if (event.ctrlKey && event.keyCode == 121) {
|
||||
try {
|
||||
app.mk._services.mediaItemPlayback._currentPlayer.stop();
|
||||
} catch (e) {}
|
||||
try {
|
||||
app.mk._services.mediaItemPlayback._currentPlayer.destroy();
|
||||
} catch (e) {}
|
||||
}
|
||||
if (event.ctrlKey && event.keyCode == 122) {
|
||||
try {
|
||||
ipcRenderer.send("detachDT", "");
|
||||
} catch (e) {}
|
||||
}
|
||||
// Prevent Scrolling on spacebar
|
||||
if (event.keyCode === 32 && event.target === document.body) {
|
||||
event.preventDefault();
|
||||
app.SpacePause();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', async function (event) {
|
||||
// CTRL + R
|
||||
if (event.keyCode === 82 && event.ctrlKey) {
|
||||
event.preventDefault()
|
||||
app.confirm(app.getLz('term.reload'), (res)=>{
|
||||
if (res) {
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
||||
}
|
||||
// CTRL + SHIFT + R
|
||||
if (event.keyCode === 82 && event.ctrlKey && event.shiftKey) {
|
||||
event.preventDefault()
|
||||
window.location.reload()
|
||||
}
|
||||
// CTRL + E
|
||||
if (event.keyCode === 69 && event.ctrlKey) {
|
||||
app.invokeDrawer('queue')
|
||||
}
|
||||
// CTRL+H
|
||||
if (event.keyCode === 72 && event.ctrlKey) {
|
||||
app.appRoute("home")
|
||||
}
|
||||
// CTRL+SHIFT+H
|
||||
if (event.ctrlKey && event.shiftKey && event.keyCode == 72) {
|
||||
let hist = await app.mk.api.v3.music(`/v1/me/recent/played/tracks`, {
|
||||
l: app.mklang
|
||||
})
|
||||
app.showCollection(hist.data, app.getLz('term.history'))
|
||||
}
|
||||
if (event.ctrlKey && event.keyCode == 121) {
|
||||
try {
|
||||
app.mk._services.mediaItemPlayback._currentPlayer.stop()
|
||||
} catch (e) {
|
||||
}
|
||||
try {
|
||||
app.mk._services.mediaItemPlayback._currentPlayer.destroy()
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
if (event.ctrlKey && event.keyCode == 122) {
|
||||
try {
|
||||
ipcRenderer.send('detachDT', '')
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
// Prevent Scrolling on spacebar
|
||||
if (event.keyCode === 32 && event.target === document.body) {
|
||||
event.preventDefault()
|
||||
app.SpacePause()
|
||||
// Hang Timer
|
||||
app.hangtimer = setTimeout(() => {
|
||||
if (confirm("Cider is not responding. Reload the app?")) {
|
||||
window.location.reload();
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Hang Timer
|
||||
app.hangtimer = setTimeout(() => {
|
||||
if (confirm("Cider is not responding. Reload the app?")) {
|
||||
window.location.reload()
|
||||
}
|
||||
}, 10000)
|
||||
|
||||
// Refresh Focus
|
||||
function refreshFocus() {
|
||||
if (document.hasFocus() == false) {
|
||||
app.windowFocus(false)
|
||||
} else {
|
||||
app.windowFocus(true)
|
||||
}
|
||||
setTimeout(refreshFocus, 200);
|
||||
}
|
||||
|
||||
refreshFocus();
|
||||
// Refresh Focus
|
||||
function refreshFocus() {
|
||||
if (document.hasFocus() == false) {
|
||||
app.windowFocus(false);
|
||||
} else {
|
||||
app.windowFocus(true);
|
||||
}
|
||||
setTimeout(refreshFocus, 200);
|
||||
}
|
||||
}
|
||||
|
||||
export {Events}
|
||||
refreshFocus();
|
||||
},
|
||||
};
|
||||
|
||||
export { Events };
|
||||
|
|
|
@ -1,327 +1,313 @@
|
|||
function simulateGamepad () {
|
||||
const app = window.app
|
||||
app.chrome.showCursor = true
|
||||
let cursorPos = [0, 0];
|
||||
let intTabIndex = 0
|
||||
const cursorSpeedPvt = 8
|
||||
const cursorSize = 16
|
||||
let scrollSpeed = 8
|
||||
let buttonPressDelay = 500
|
||||
let stickDeadZone = 0.2
|
||||
let scrollGroup = null
|
||||
let scrollGroupY = null
|
||||
let elementFocusEnabled = true
|
||||
let start;
|
||||
function simulateGamepad() {
|
||||
const app = window.app;
|
||||
app.chrome.showCursor = true;
|
||||
let cursorPos = [0, 0];
|
||||
let intTabIndex = 0;
|
||||
const cursorSpeedPvt = 8;
|
||||
const cursorSize = 16;
|
||||
let scrollSpeed = 8;
|
||||
let buttonPressDelay = 500;
|
||||
let stickDeadZone = 0.2;
|
||||
let scrollGroup = null;
|
||||
let scrollGroupY = null;
|
||||
let elementFocusEnabled = true;
|
||||
let start;
|
||||
|
||||
let cursorSpeed = cursorSpeedPvt
|
||||
let cursorSpeed = cursorSpeedPvt;
|
||||
|
||||
let lastButtonPress = {
|
||||
let lastButtonPress = {};
|
||||
|
||||
var sounds = {
|
||||
Confirm: new Audio("./sounds/confirm.ogg"),
|
||||
Menu: new Audio("./sounds/btn1.ogg"),
|
||||
Hover: new Audio("./sounds/hover.ogg"),
|
||||
};
|
||||
|
||||
let element = document.elementFromPoint(0, 0);
|
||||
let elementType = 0;
|
||||
|
||||
function appLoop() {
|
||||
var gamepads = navigator.getGamepads ? navigator.getGamepads() : navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : [];
|
||||
if (!gamepads) {
|
||||
return;
|
||||
}
|
||||
|
||||
var sounds = {
|
||||
Confirm: new Audio("./sounds/confirm.ogg"),
|
||||
Menu: new Audio("./sounds/btn1.ogg"),
|
||||
Hover: new Audio("./sounds/hover.ogg")
|
||||
var gp = gamepads[0];
|
||||
|
||||
// LEFT STICK
|
||||
if (gp.axes[0] > stickDeadZone) {
|
||||
cursorPos[0] += gp.axes[0] * cursorSpeed;
|
||||
} else if (gp.axes[0] < -stickDeadZone) {
|
||||
cursorPos[0] += gp.axes[0] * cursorSpeed;
|
||||
}
|
||||
|
||||
let element = document.elementFromPoint(0, 0)
|
||||
let elementType = 0
|
||||
|
||||
function appLoop() {
|
||||
var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []);
|
||||
if (!gamepads) {
|
||||
return;
|
||||
}
|
||||
|
||||
var gp = gamepads[0];
|
||||
|
||||
// LEFT STICK
|
||||
if (gp.axes[0] > stickDeadZone) {
|
||||
cursorPos[0] += (gp.axes[0] * cursorSpeed)
|
||||
} else if (gp.axes[0] < -stickDeadZone) {
|
||||
cursorPos[0] += (gp.axes[0] * cursorSpeed)
|
||||
}
|
||||
|
||||
if (gp.axes[1] > stickDeadZone) {
|
||||
cursorPos[1] += (gp.axes[1] * cursorSpeed)
|
||||
} else if (gp.axes[1] < -stickDeadZone) {
|
||||
cursorPos[1] += (gp.axes[1] * cursorSpeed)
|
||||
}
|
||||
|
||||
if (cursorPos[0] < cursorSize) {
|
||||
cursorPos[0] = cursorSize
|
||||
}
|
||||
if (cursorPos[1] < cursorSize) {
|
||||
cursorPos[1] = cursorSize
|
||||
}
|
||||
if (cursorPos[0] > window.innerWidth - cursorSize) {
|
||||
cursorPos[0] = window.innerWidth - cursorSize
|
||||
}
|
||||
if (cursorPos[1] > window.innerHeight - cursorSize) {
|
||||
cursorPos[1] = window.innerHeight - cursorSize
|
||||
}
|
||||
|
||||
|
||||
// RIGHT STICK.
|
||||
if (scrollGroupY) {
|
||||
if (gp.axes[3] > stickDeadZone) {
|
||||
$(scrollGroupY).scrollTop($(scrollGroupY).scrollTop() + (gp.axes[3] * scrollSpeed))
|
||||
elementFocusEnabled = false
|
||||
} else if (gp.axes[3] < -stickDeadZone) {
|
||||
$(scrollGroupY).scrollTop($(scrollGroupY).scrollTop() + (gp.axes[3] * scrollSpeed))
|
||||
elementFocusEnabled = false
|
||||
} else {
|
||||
elementFocusEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (scrollGroup) {
|
||||
if (gp.axes[2] > stickDeadZone) {
|
||||
$(scrollGroup).scrollLeft($(scrollGroup).scrollLeft() + (gp.axes[2] * scrollSpeed))
|
||||
elementFocusEnabled = false
|
||||
} else if (gp.axes[2] < -stickDeadZone) {
|
||||
$(scrollGroup).scrollLeft($(scrollGroup).scrollLeft() + (gp.axes[2] * scrollSpeed))
|
||||
elementFocusEnabled = false
|
||||
} else {
|
||||
elementFocusEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$(".cursor").css({
|
||||
top: cursorPos[1] + "px",
|
||||
left: cursorPos[0] + "px",
|
||||
display: "block"
|
||||
})
|
||||
|
||||
// A BUTTON
|
||||
if (gp.buttons[0].pressed) {
|
||||
if (!lastButtonPress["A"]) {
|
||||
lastButtonPress["A"] = 0
|
||||
}
|
||||
if (Date.now() - lastButtonPress["A"] > buttonPressDelay) {
|
||||
lastButtonPress["A"] = Date.now()
|
||||
sounds.Confirm.play()
|
||||
if (elementType == 0) {
|
||||
document.activeElement.dispatchEvent(new Event("click"))
|
||||
document.activeElement.dispatchEvent(new Event("controller-click"))
|
||||
} else {
|
||||
element.dispatchEvent(new Event("click"))
|
||||
element.dispatchEvent(new Event("controller-click"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// B BUTTON
|
||||
if (gp.buttons[1].pressed) {
|
||||
|
||||
if (!lastButtonPress["B"]) {
|
||||
lastButtonPress["B"] = 0
|
||||
}
|
||||
if (Date.now() - lastButtonPress["B"] > buttonPressDelay) {
|
||||
lastButtonPress["B"] = Date.now()
|
||||
if (elementType == 0) {
|
||||
document.activeElement.dispatchEvent(new Event("contextmenu"))
|
||||
setTimeout(() => {
|
||||
if ($(".menu-option").length > 0) {
|
||||
let bounds = $(".menu-option")[0].getBoundingClientRect()
|
||||
cursorPos[0] = bounds.left + (bounds.width / 2)
|
||||
cursorPos[1] = bounds.top + (bounds.height / 2)
|
||||
}
|
||||
}, 100)
|
||||
} else {
|
||||
element.dispatchEvent(new Event("contextmenu"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// right bumper
|
||||
if (gp.buttons[5].pressed) {
|
||||
if (!lastButtonPress["RB"]) {
|
||||
lastButtonPress["RB"] = 0
|
||||
}
|
||||
if (Date.now() - lastButtonPress["RB"] > buttonPressDelay) {
|
||||
lastButtonPress["RB"] = Date.now()
|
||||
app.navigateForward()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// left bumper
|
||||
if (gp.buttons[4].pressed) {
|
||||
if (!lastButtonPress["LB"]) {
|
||||
lastButtonPress["LB"] = 0
|
||||
}
|
||||
if (Date.now() - lastButtonPress["LB"] > buttonPressDelay) {
|
||||
lastButtonPress["LB"] = Date.now()
|
||||
app.navigateBack()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// cursor hover
|
||||
if (elementFocusEnabled) {
|
||||
element = document.elementFromPoint(cursorPos[0], cursorPos[1])
|
||||
}
|
||||
|
||||
if (element) {
|
||||
|
||||
let closest = element.closest("[tabindex], input, button, a")
|
||||
|
||||
// VERT SCROLL
|
||||
let scrollGroupCloY = element.closest(`[scrollaxis="y"]`)
|
||||
if (scrollGroupCloY) {
|
||||
scrollGroupY = scrollGroupCloY
|
||||
}
|
||||
|
||||
|
||||
// HOZ SCROLL
|
||||
let scrollGroupClo = element.closest(".v-hl-container")
|
||||
|
||||
if (scrollGroupClo) {
|
||||
if (scrollGroupClo.classList.contains("v-hl-container")) {
|
||||
scrollGroup = scrollGroupClo
|
||||
scrollGroup.style["scroll-snap-type"] = "unset"
|
||||
} else {
|
||||
scrollGroup.style["scroll-snap-type"] = ""
|
||||
scrollGroup = null
|
||||
}
|
||||
}
|
||||
|
||||
if (closest) {
|
||||
elementType = 0
|
||||
closest.focus()
|
||||
} else {
|
||||
if (closest) {
|
||||
closest.blur()
|
||||
}
|
||||
elementType = 1
|
||||
element.focus()
|
||||
}
|
||||
cursorSpeed = cursorSpeedPvt
|
||||
if (!element.classList.contains("app-chrome")
|
||||
&& !element.classList.contains("app-content")) {
|
||||
cursorSpeed = cursorSpeedPvt
|
||||
}
|
||||
// console.log($._data($(element), "events"))
|
||||
} else {
|
||||
cursorSpeed = 12
|
||||
}
|
||||
// console.log(gp.axes[0], gp.axes[1])
|
||||
start = requestAnimationFrame(appLoop);
|
||||
if (gp.axes[1] > stickDeadZone) {
|
||||
cursorPos[1] += gp.axes[1] * cursorSpeed;
|
||||
} else if (gp.axes[1] < -stickDeadZone) {
|
||||
cursorPos[1] += gp.axes[1] * cursorSpeed;
|
||||
}
|
||||
|
||||
// controller pairing
|
||||
notyf.error("Press the button on your controller to pair it to Cider.")
|
||||
window.addEventListener("gamepadconnected", function (e) {
|
||||
console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
|
||||
e.gamepad.index, e.gamepad.id,
|
||||
e.gamepad.buttons.length, e.gamepad.axes.length);
|
||||
notyf.success("Pairing successful!")
|
||||
appLoop()
|
||||
}, { once: true });
|
||||
if (cursorPos[0] < cursorSize) {
|
||||
cursorPos[0] = cursorSize;
|
||||
}
|
||||
if (cursorPos[1] < cursorSize) {
|
||||
cursorPos[1] = cursorSize;
|
||||
}
|
||||
if (cursorPos[0] > window.innerWidth - cursorSize) {
|
||||
cursorPos[0] = window.innerWidth - cursorSize;
|
||||
}
|
||||
if (cursorPos[1] > window.innerHeight - cursorSize) {
|
||||
cursorPos[1] = window.innerHeight - cursorSize;
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
sounds.Confirm.currentTime = 0
|
||||
sounds.Menu.currentTime = 0
|
||||
sounds.Hover.currentTime = 0
|
||||
let tabbable = $("[tabindex]")
|
||||
console.log(e.key)
|
||||
switch (e.key) {
|
||||
default:
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
e.preventDefault()
|
||||
// RIGHT STICK.
|
||||
if (scrollGroupY) {
|
||||
if (gp.axes[3] > stickDeadZone) {
|
||||
$(scrollGroupY).scrollTop($(scrollGroupY).scrollTop() + gp.axes[3] * scrollSpeed);
|
||||
elementFocusEnabled = false;
|
||||
} else if (gp.axes[3] < -stickDeadZone) {
|
||||
$(scrollGroupY).scrollTop($(scrollGroupY).scrollTop() + gp.axes[3] * scrollSpeed);
|
||||
elementFocusEnabled = false;
|
||||
} else {
|
||||
elementFocusEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
cursorPos[0] -= cursorSpeed
|
||||
break;
|
||||
case "ArrowRight":
|
||||
e.preventDefault()
|
||||
if (scrollGroup) {
|
||||
if (gp.axes[2] > stickDeadZone) {
|
||||
$(scrollGroup).scrollLeft($(scrollGroup).scrollLeft() + gp.axes[2] * scrollSpeed);
|
||||
elementFocusEnabled = false;
|
||||
} else if (gp.axes[2] < -stickDeadZone) {
|
||||
$(scrollGroup).scrollLeft($(scrollGroup).scrollLeft() + gp.axes[2] * scrollSpeed);
|
||||
elementFocusEnabled = false;
|
||||
} else {
|
||||
elementFocusEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
cursorPos[0] += cursorSpeed
|
||||
break;
|
||||
case "ArrowUp":
|
||||
e.preventDefault()
|
||||
|
||||
cursorPos[1] -= cursorSpeed
|
||||
// sounds.Hover.play()
|
||||
// if (intTabIndex <= 0) {
|
||||
// intTabIndex = 0
|
||||
// } else {
|
||||
// intTabIndex--
|
||||
// }
|
||||
// $(tabbable[intTabIndex]).focus()
|
||||
// $("#app-content").scrollTop($(document.activeElement).offset().top)
|
||||
break;
|
||||
case "ArrowDown":
|
||||
e.preventDefault()
|
||||
|
||||
cursorPos[1] += cursorSpeed
|
||||
// if (intTabIndex < tabbable.length) {
|
||||
// intTabIndex++
|
||||
// } else {
|
||||
// intTabIndex = tabbable.length
|
||||
// }
|
||||
// $(tabbable[intTabIndex]).focus()
|
||||
// $("#app-content").scrollTop($(document.activeElement).offset().top)
|
||||
break;
|
||||
case "c":
|
||||
app.resetState()
|
||||
break;
|
||||
case "x":
|
||||
// set cursorPos to the top right of the screen
|
||||
// sounds.Menu.play()
|
||||
if (elementType == 0) {
|
||||
document.activeElement.dispatchEvent(new Event("contextmenu"))
|
||||
} else {
|
||||
element.dispatchEvent(new Event("contextmenu"))
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
break;
|
||||
case "z":
|
||||
sounds.Confirm.play()
|
||||
if (elementType == 0) {
|
||||
document.activeElement.dispatchEvent(new Event("click"))
|
||||
document.activeElement.dispatchEvent(new Event("controller-click"))
|
||||
} else {
|
||||
element.dispatchEvent(new Event("click"))
|
||||
element.dispatchEvent(new Event("controller-click"))
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
break;
|
||||
}
|
||||
|
||||
$(".cursor").css({
|
||||
top: cursorPos[1] + "px",
|
||||
left: cursorPos[0] + "px"
|
||||
})
|
||||
function lerp(a, b, n) {
|
||||
return (1 - n) * a + n * b
|
||||
}
|
||||
|
||||
|
||||
element = document.elementFromPoint(cursorPos[0], cursorPos[1])
|
||||
|
||||
if (element) {
|
||||
let closest = element.closest("[tabindex], input, button, a")
|
||||
if (closest) {
|
||||
elementType = 0
|
||||
closest.focus()
|
||||
} else {
|
||||
elementType = 1
|
||||
element.focus()
|
||||
}
|
||||
}
|
||||
console.log(element)
|
||||
$(".cursor").css({
|
||||
top: cursorPos[1] + "px",
|
||||
left: cursorPos[0] + "px",
|
||||
display: "block",
|
||||
});
|
||||
|
||||
// A BUTTON
|
||||
if (gp.buttons[0].pressed) {
|
||||
if (!lastButtonPress["A"]) {
|
||||
lastButtonPress["A"] = 0;
|
||||
}
|
||||
if (Date.now() - lastButtonPress["A"] > buttonPressDelay) {
|
||||
lastButtonPress["A"] = Date.now();
|
||||
sounds.Confirm.play();
|
||||
if (elementType == 0) {
|
||||
document.activeElement.dispatchEvent(new Event("click"));
|
||||
document.activeElement.dispatchEvent(new Event("controller-click"));
|
||||
} else {
|
||||
element.dispatchEvent(new Event("click"));
|
||||
element.dispatchEvent(new Event("controller-click"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// B BUTTON
|
||||
if (gp.buttons[1].pressed) {
|
||||
if (!lastButtonPress["B"]) {
|
||||
lastButtonPress["B"] = 0;
|
||||
}
|
||||
if (Date.now() - lastButtonPress["B"] > buttonPressDelay) {
|
||||
lastButtonPress["B"] = Date.now();
|
||||
if (elementType == 0) {
|
||||
document.activeElement.dispatchEvent(new Event("contextmenu"));
|
||||
setTimeout(() => {
|
||||
if ($(".menu-option").length > 0) {
|
||||
let bounds = $(".menu-option")[0].getBoundingClientRect();
|
||||
cursorPos[0] = bounds.left + bounds.width / 2;
|
||||
cursorPos[1] = bounds.top + bounds.height / 2;
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
element.dispatchEvent(new Event("contextmenu"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// right bumper
|
||||
if (gp.buttons[5].pressed) {
|
||||
if (!lastButtonPress["RB"]) {
|
||||
lastButtonPress["RB"] = 0;
|
||||
}
|
||||
if (Date.now() - lastButtonPress["RB"] > buttonPressDelay) {
|
||||
lastButtonPress["RB"] = Date.now();
|
||||
app.navigateForward();
|
||||
}
|
||||
}
|
||||
|
||||
// left bumper
|
||||
if (gp.buttons[4].pressed) {
|
||||
if (!lastButtonPress["LB"]) {
|
||||
lastButtonPress["LB"] = 0;
|
||||
}
|
||||
if (Date.now() - lastButtonPress["LB"] > buttonPressDelay) {
|
||||
lastButtonPress["LB"] = Date.now();
|
||||
app.navigateBack();
|
||||
}
|
||||
}
|
||||
|
||||
// cursor hover
|
||||
if (elementFocusEnabled) {
|
||||
element = document.elementFromPoint(cursorPos[0], cursorPos[1]);
|
||||
}
|
||||
|
||||
if (element) {
|
||||
let closest = element.closest("[tabindex], input, button, a");
|
||||
|
||||
// VERT SCROLL
|
||||
let scrollGroupCloY = element.closest(`[scrollaxis="y"]`);
|
||||
if (scrollGroupCloY) {
|
||||
scrollGroupY = scrollGroupCloY;
|
||||
}
|
||||
|
||||
// HOZ SCROLL
|
||||
let scrollGroupClo = element.closest(".v-hl-container");
|
||||
|
||||
if (scrollGroupClo) {
|
||||
if (scrollGroupClo.classList.contains("v-hl-container")) {
|
||||
scrollGroup = scrollGroupClo;
|
||||
scrollGroup.style["scroll-snap-type"] = "unset";
|
||||
} else {
|
||||
scrollGroup.style["scroll-snap-type"] = "";
|
||||
scrollGroup = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (closest) {
|
||||
elementType = 0;
|
||||
closest.focus();
|
||||
} else {
|
||||
if (closest) {
|
||||
closest.blur();
|
||||
}
|
||||
elementType = 1;
|
||||
element.focus();
|
||||
}
|
||||
cursorSpeed = cursorSpeedPvt;
|
||||
if (!element.classList.contains("app-chrome") && !element.classList.contains("app-content")) {
|
||||
cursorSpeed = cursorSpeedPvt;
|
||||
}
|
||||
// console.log($._data($(element), "events"))
|
||||
} else {
|
||||
cursorSpeed = 12;
|
||||
}
|
||||
// console.log(gp.axes[0], gp.axes[1])
|
||||
start = requestAnimationFrame(appLoop);
|
||||
}
|
||||
|
||||
// controller pairing
|
||||
notyf.error("Press the button on your controller to pair it to Cider.");
|
||||
window.addEventListener(
|
||||
"gamepadconnected",
|
||||
function (e) {
|
||||
console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length);
|
||||
notyf.success("Pairing successful!");
|
||||
appLoop();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
sounds.Confirm.currentTime = 0;
|
||||
sounds.Menu.currentTime = 0;
|
||||
sounds.Hover.currentTime = 0;
|
||||
let tabbable = $("[tabindex]");
|
||||
console.log(e.key);
|
||||
switch (e.key) {
|
||||
default:
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
e.preventDefault();
|
||||
|
||||
cursorPos[0] -= cursorSpeed;
|
||||
break;
|
||||
case "ArrowRight":
|
||||
e.preventDefault();
|
||||
|
||||
cursorPos[0] += cursorSpeed;
|
||||
break;
|
||||
case "ArrowUp":
|
||||
e.preventDefault();
|
||||
|
||||
cursorPos[1] -= cursorSpeed;
|
||||
// sounds.Hover.play()
|
||||
// if (intTabIndex <= 0) {
|
||||
// intTabIndex = 0
|
||||
// } else {
|
||||
// intTabIndex--
|
||||
// }
|
||||
// $(tabbable[intTabIndex]).focus()
|
||||
// $("#app-content").scrollTop($(document.activeElement).offset().top)
|
||||
break;
|
||||
case "ArrowDown":
|
||||
e.preventDefault();
|
||||
|
||||
cursorPos[1] += cursorSpeed;
|
||||
// if (intTabIndex < tabbable.length) {
|
||||
// intTabIndex++
|
||||
// } else {
|
||||
// intTabIndex = tabbable.length
|
||||
// }
|
||||
// $(tabbable[intTabIndex]).focus()
|
||||
// $("#app-content").scrollTop($(document.activeElement).offset().top)
|
||||
break;
|
||||
case "c":
|
||||
app.resetState();
|
||||
break;
|
||||
case "x":
|
||||
// set cursorPos to the top right of the screen
|
||||
// sounds.Menu.play()
|
||||
if (elementType == 0) {
|
||||
document.activeElement.dispatchEvent(new Event("contextmenu"));
|
||||
} else {
|
||||
element.dispatchEvent(new Event("contextmenu"));
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
break;
|
||||
case "z":
|
||||
sounds.Confirm.play();
|
||||
if (elementType == 0) {
|
||||
document.activeElement.dispatchEvent(new Event("click"));
|
||||
document.activeElement.dispatchEvent(new Event("controller-click"));
|
||||
} else {
|
||||
element.dispatchEvent(new Event("click"));
|
||||
element.dispatchEvent(new Event("controller-click"));
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
$(".cursor").css({
|
||||
top: cursorPos[1] + "px",
|
||||
left: cursorPos[0] + "px",
|
||||
});
|
||||
function lerp(a, b, n) {
|
||||
return (1 - n) * a + n * b;
|
||||
}
|
||||
|
||||
element = document.elementFromPoint(cursorPos[0], cursorPos[1]);
|
||||
|
||||
if (element) {
|
||||
let closest = element.closest("[tabindex], input, button, a");
|
||||
if (closest) {
|
||||
elementType = 0;
|
||||
closest.focus();
|
||||
} else {
|
||||
elementType = 1;
|
||||
element.focus();
|
||||
}
|
||||
}
|
||||
console.log(element);
|
||||
});
|
||||
}
|
||||
|
||||
export {simulateGamepad}
|
||||
export { simulateGamepad };
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export function html (str) {
|
||||
return str[0]
|
||||
}
|
||||
export function html(str) {
|
||||
return str[0];
|
||||
}
|
||||
|
|
|
@ -31,9 +31,9 @@ async function spawnMica() {
|
|||
}
|
||||
if (micaCache.path == imgSrc.path) {
|
||||
imgSrc = micaCache;
|
||||
}else{
|
||||
} else {
|
||||
imgSrc = await ipcRenderer.sendSync("get-wallpaper", {
|
||||
blurAmount: 256
|
||||
blurAmount: 256,
|
||||
});
|
||||
CiderCache.putCache("mica-cache", imgSrc);
|
||||
}
|
||||
|
@ -51,10 +51,7 @@ async function spawnMica() {
|
|||
cb();
|
||||
}
|
||||
// window size change
|
||||
if (
|
||||
lastScreenWidth !== window.innerWidth ||
|
||||
lastScreenHeight !== window.innerHeight
|
||||
) {
|
||||
if (lastScreenWidth !== window.innerWidth || lastScreenHeight !== window.innerHeight) {
|
||||
lastScreenWidth = window.innerWidth;
|
||||
lastScreenHeight = window.innerHeight;
|
||||
cb();
|
||||
|
|
|
@ -1,56 +1,44 @@
|
|||
const MusicKitTools = {
|
||||
async v3Backend({
|
||||
route = "", getBody = {}, options = {}
|
||||
}) {
|
||||
return await (await ipcRenderer.invoke("mkv3", {
|
||||
token: MusicKit.getInstance().developerToken,
|
||||
route: route,
|
||||
mediaToken: MusicKit.getInstance().musicUserToken,
|
||||
GETBody: getBody
|
||||
}))
|
||||
},
|
||||
async v3Continuous({
|
||||
href,
|
||||
options = {},
|
||||
reqOptions = {},
|
||||
onProgress = () => {
|
||||
},
|
||||
onError = () => {
|
||||
},
|
||||
onSuccess = () => {
|
||||
}
|
||||
} = {}) {
|
||||
let returnData = []
|
||||
async v3Backend({ route = "", getBody = {}, options = {} }) {
|
||||
return await await ipcRenderer.invoke("mkv3", {
|
||||
token: MusicKit.getInstance().developerToken,
|
||||
route: route,
|
||||
mediaToken: MusicKit.getInstance().musicUserToken,
|
||||
GETBody: getBody,
|
||||
});
|
||||
},
|
||||
async v3Continuous({ href, options = {}, reqOptions = {}, onProgress = () => {}, onError = () => {}, onSuccess = () => {} } = {}) {
|
||||
let returnData = [];
|
||||
|
||||
async function sendReq(href, options) {
|
||||
const response = await app.mk.api.v3.music(href, options).catch(error => onError)
|
||||
async function sendReq(href, options) {
|
||||
const response = await app.mk.api.v3.music(href, options).catch((error) => onError);
|
||||
|
||||
returnData = returnData.concat(response.data.data)
|
||||
if (response.data.next) {
|
||||
onProgress({
|
||||
response: response,
|
||||
total: returnData.length
|
||||
})
|
||||
try {
|
||||
await sendReq(response.data.next, options)
|
||||
} catch (e) {
|
||||
await sendReq(response.data.next, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await sendReq(href, options)
|
||||
onSuccess(returnData)
|
||||
return returnData
|
||||
},
|
||||
getHeader() {
|
||||
return new Headers({
|
||||
Authorization: 'Bearer ' + MusicKit.getInstance().developerToken,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Music-User-Token': '' + MusicKit.getInstance().musicUserToken
|
||||
returnData = returnData.concat(response.data.data);
|
||||
if (response.data.next) {
|
||||
onProgress({
|
||||
response: response,
|
||||
total: returnData.length,
|
||||
});
|
||||
try {
|
||||
await sendReq(response.data.next, options);
|
||||
} catch (e) {
|
||||
await sendReq(response.data.next, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {MusicKitTools}
|
||||
await sendReq(href, options);
|
||||
onSuccess(returnData);
|
||||
return returnData;
|
||||
},
|
||||
getHeader() {
|
||||
return new Headers({
|
||||
Authorization: "Bearer " + MusicKit.getInstance().developerToken,
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Music-User-Token": "" + MusicKit.getInstance().musicUserToken,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export { MusicKitTools };
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,38 +1,38 @@
|
|||
const store = new Vuex.Store({
|
||||
state: {
|
||||
windowRelativeScale: 1,
|
||||
library: {
|
||||
// songs: ipcRenderer.sendSync("get-library-songs"),
|
||||
// albums: ipcRenderer.sendSync("get-library-albums"),
|
||||
// recentlyAdded: ipcRenderer.sendSync("get-library-recentlyAdded"),
|
||||
// playlists: ipcRenderer.sendSync("get-library-playlists")
|
||||
},
|
||||
pageState: {
|
||||
recentlyAdded: {
|
||||
loaded: false,
|
||||
nextUrl: null,
|
||||
items: [],
|
||||
size: "normal"
|
||||
},
|
||||
settings: {
|
||||
currentTabIndex: 0,
|
||||
fullscreen: false
|
||||
}
|
||||
},
|
||||
artwork: {
|
||||
playerLCD: ""
|
||||
}
|
||||
state: {
|
||||
windowRelativeScale: 1,
|
||||
library: {
|
||||
// songs: ipcRenderer.sendSync("get-library-songs"),
|
||||
// albums: ipcRenderer.sendSync("get-library-albums"),
|
||||
// recentlyAdded: ipcRenderer.sendSync("get-library-recentlyAdded"),
|
||||
// playlists: ipcRenderer.sendSync("get-library-playlists")
|
||||
},
|
||||
mutations: {
|
||||
resetRecentlyAdded(state) {
|
||||
state.pageState.recentlyAdded.loaded = false;
|
||||
state.pageState.recentlyAdded.nextUrl = null;
|
||||
state.pageState.recentlyAdded.items = [];
|
||||
},
|
||||
setLCDArtwork(state, artwork) {
|
||||
state.artwork.playerLCD = artwork
|
||||
}
|
||||
}
|
||||
})
|
||||
pageState: {
|
||||
recentlyAdded: {
|
||||
loaded: false,
|
||||
nextUrl: null,
|
||||
items: [],
|
||||
size: "normal",
|
||||
},
|
||||
settings: {
|
||||
currentTabIndex: 0,
|
||||
fullscreen: false,
|
||||
},
|
||||
},
|
||||
artwork: {
|
||||
playerLCD: "",
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
resetRecentlyAdded(state) {
|
||||
state.pageState.recentlyAdded.loaded = false;
|
||||
state.pageState.recentlyAdded.nextUrl = null;
|
||||
state.pageState.recentlyAdded.items = [];
|
||||
},
|
||||
setLCDArtwork(state, artwork) {
|
||||
state.artwork.playerLCD = artwork;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export {store}
|
||||
export { store };
|
||||
|
|
|
@ -1,194 +1,230 @@
|
|||
const wsapi = {
|
||||
cache: {playParams: {id: 0}, status: null, remainingTime: 0},
|
||||
playbackCache: {status: null, time: Date.now()},
|
||||
async v3(encoded = "") {
|
||||
let decoded = atob(encoded);
|
||||
let json = JSON.parse(decoded);
|
||||
console.log(json)
|
||||
let response = await (await MusicKit.getInstance().api.v3.music(json.route, json.body, json.options))
|
||||
let ret = response.data
|
||||
return JSON.stringify(ret)
|
||||
},
|
||||
search(term, limit) {
|
||||
MusicKit.getInstance().api.search(term, {limit: limit, types: 'songs,artists,albums,playlists'}).then((results)=>{
|
||||
ipcRenderer.send('wsapi-returnSearch', JSON.stringify(results))
|
||||
})
|
||||
},
|
||||
searchLibrary(term, limit) {
|
||||
MusicKit.getInstance().api.library.search(term, {limit: limit, types: 'library-songs,library-artists,library-albums,library-playlists'}).then((results)=>{
|
||||
ipcRenderer.send('wsapi-returnSearchLibrary', JSON.stringify(results))
|
||||
})
|
||||
},
|
||||
getAttributes: function () {
|
||||
const mk = MusicKit.getInstance();
|
||||
const nowPlayingItem = mk.nowPlayingItem;
|
||||
const isPlayingExport = mk.isPlaying;
|
||||
const remainingTimeExport = mk.currentPlaybackTimeRemaining;
|
||||
const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {});
|
||||
cache: { playParams: { id: 0 }, status: null, remainingTime: 0 },
|
||||
playbackCache: { status: null, time: Date.now() },
|
||||
async v3(encoded = "") {
|
||||
let decoded = atob(encoded);
|
||||
let json = JSON.parse(decoded);
|
||||
console.log(json);
|
||||
let response = await await MusicKit.getInstance().api.v3.music(json.route, json.body, json.options);
|
||||
let ret = response.data;
|
||||
return JSON.stringify(ret);
|
||||
},
|
||||
search(term, limit) {
|
||||
MusicKit.getInstance()
|
||||
.api.search(term, {
|
||||
limit: limit,
|
||||
types: "songs,artists,albums,playlists",
|
||||
})
|
||||
.then((results) => {
|
||||
ipcRenderer.send("wsapi-returnSearch", JSON.stringify(results));
|
||||
});
|
||||
},
|
||||
searchLibrary(term, limit) {
|
||||
MusicKit.getInstance()
|
||||
.api.library.search(term, {
|
||||
limit: limit,
|
||||
types: "library-songs,library-artists,library-albums,library-playlists",
|
||||
})
|
||||
.then((results) => {
|
||||
ipcRenderer.send("wsapi-returnSearchLibrary", JSON.stringify(results));
|
||||
});
|
||||
},
|
||||
getAttributes: function () {
|
||||
const mk = MusicKit.getInstance();
|
||||
const nowPlayingItem = mk.nowPlayingItem;
|
||||
const isPlayingExport = mk.isPlaying;
|
||||
const remainingTimeExport = mk.currentPlaybackTimeRemaining;
|
||||
const attributes = nowPlayingItem != null ? nowPlayingItem.attributes : {};
|
||||
|
||||
attributes.status = isPlayingExport ? isPlayingExport : false;
|
||||
attributes.name = attributes.name ? attributes.name : 'No Title Found';
|
||||
attributes.artwork = attributes.artwork ? attributes.artwork : {url: ''};
|
||||
attributes.artwork.url = attributes.artwork.url ? attributes.artwork.url : '';
|
||||
attributes.playParams = attributes.playParams ? attributes.playParams : {id: 'no-id-found'};
|
||||
attributes.playParams.id = attributes.playParams.id ? attributes.playParams.id : 'no-id-found';
|
||||
attributes.albumName = attributes.albumName ? attributes.albumName : '';
|
||||
attributes.artistName = attributes.artistName ? attributes.artistName : '';
|
||||
attributes.genreNames = attributes.genreNames ? attributes.genreNames : [];
|
||||
attributes.remainingTime = remainingTimeExport ? (remainingTimeExport * 1000) : 0;
|
||||
attributes.durationInMillis = attributes.durationInMillis ? attributes.durationInMillis : 0;
|
||||
attributes.startTime = Date.now();
|
||||
attributes.endTime = attributes.endTime ? attributes.endTime : Date.now();
|
||||
attributes.volume = mk.volume;
|
||||
attributes.shuffleMode = mk.shuffleMode;
|
||||
attributes.repeatMode = mk.repeatMode;
|
||||
attributes.autoplayEnabled = mk.autoplayEnabled;
|
||||
return attributes
|
||||
},
|
||||
moveQueueItem(oldPosition, newPosition) {
|
||||
MusicKit.getInstance().queue._queueItems.splice(newPosition,0,MusicKit.getInstance().queue._queueItems.splice(oldPosition,1)[0])
|
||||
MusicKit.getInstance().queue._reindex()
|
||||
},
|
||||
setAutoplay(value) {
|
||||
MusicKit.getInstance().autoplayEnabled = value
|
||||
},
|
||||
returnDynamic(data, type) {
|
||||
ipcRenderer.send('wsapi-returnDynamic', JSON.stringify(data), type)
|
||||
},
|
||||
musickitApi(method, id, params, library = false) {
|
||||
if (library) {
|
||||
MusicKit.getInstance().api.library[method](id, params).then((results)=>{
|
||||
ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method)
|
||||
})
|
||||
} else {
|
||||
MusicKit.getInstance().api[method](id, params).then((results)=>{
|
||||
ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method)
|
||||
})
|
||||
}
|
||||
},
|
||||
getPlaybackState () {
|
||||
ipcRenderer.send('wsapi-updatePlaybackState', MusicKitInterop.getAttributes());
|
||||
},
|
||||
getLyrics() {
|
||||
ipcRenderer.send('wsapi-returnLyrics',JSON.stringify(app.lyrics));
|
||||
},
|
||||
getQueue() {
|
||||
ipcRenderer.send('wsapi-returnQueue', JSON.stringify(MusicKit.getInstance().queue))
|
||||
},
|
||||
playNext(type, id) {
|
||||
var request = {}
|
||||
request[type] = id
|
||||
MusicKit.getInstance().playNext(request)
|
||||
},
|
||||
playLater(type, id) {
|
||||
var request = {}
|
||||
request[type] = id
|
||||
MusicKit.getInstance().playLater(request)
|
||||
},
|
||||
love() {
|
||||
|
||||
},
|
||||
playTrackById(id, kind = "song") {
|
||||
MusicKit.getInstance().setQueue({ [kind]: id , parameters : {l : app.mklang}}).then(function (queue) {
|
||||
MusicKit.getInstance().play()
|
||||
})
|
||||
},
|
||||
quickPlay(term) {
|
||||
// Quick play by song name
|
||||
MusicKit.getInstance().api.search(term, { limit: 2, types: 'songs' }).then(function (data) {
|
||||
MusicKit.getInstance().setQueue({ song: data["songs"][0]["id"],parameters : {l : app.mklang} }).then(function (queue) {
|
||||
MusicKit.getInstance().play()
|
||||
})
|
||||
})
|
||||
},
|
||||
toggleShuffle() {
|
||||
MusicKit.getInstance().shuffleMode = MusicKit.getInstance().shuffleMode === 0 ? 1 : 0
|
||||
},
|
||||
togglePlayPause() {
|
||||
app.mk.isPlaying ? app.mk.pause() : app.mk.play()
|
||||
},
|
||||
toggleRepeat() {
|
||||
if (MusicKit.getInstance().repeatMode == 0) {
|
||||
MusicKit.getInstance().repeatMode = 1
|
||||
} else if (MusicKit.getInstance().repeatMode == 1){
|
||||
MusicKit.getInstance().repeatMode = 2
|
||||
} else {
|
||||
MusicKit.getInstance().repeatMode = 0
|
||||
}
|
||||
},
|
||||
getmaxVolume() {
|
||||
ipcRenderer.send('wsapi-returnvolumeMax',JSON.stringify(app.cfg.audio.maxVolume));
|
||||
},
|
||||
getLibraryStatus(kind, id) {
|
||||
if (kind === undefined || id === "no-id-found") return;
|
||||
|
||||
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
||||
app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/?ids[${truekind}]=${id}`, {
|
||||
relate: "library",
|
||||
fields: "inLibrary"
|
||||
}).then(data => {
|
||||
const res = data.data.data[0];
|
||||
const inLibrary = res && res.attributes && res.attributes.inLibrary;
|
||||
|
||||
app.getRating({ type: truekind, id: id }).then(rating => {
|
||||
ipcRenderer.send('wsapi-libraryStatus', inLibrary, rating);
|
||||
})
|
||||
})
|
||||
},
|
||||
rate(kind, id, rating) {
|
||||
if (kind === undefined || id === "no-id-found") return;
|
||||
|
||||
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
||||
|
||||
if (rating === 0) {
|
||||
app.mk.api.v3.music(`/v1/me/ratings/${truekind}/${id}`, {}, {
|
||||
fetchOptions: {
|
||||
method: "DELETE",
|
||||
}
|
||||
}).then(function () {
|
||||
ipcRenderer.send('wsapi-rate', kind, id, rating);
|
||||
})
|
||||
} else {
|
||||
app.mk.api.v3.music(`/v1/me/ratings/${truekind}/${id}`, {}, {
|
||||
fetchOptions: {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
"type": "rating",
|
||||
"attributes": {
|
||||
"value": rating
|
||||
}
|
||||
})
|
||||
}
|
||||
}).then(function () {
|
||||
ipcRenderer.send('wsapi-rate', kind, id, rating);
|
||||
})
|
||||
}
|
||||
},
|
||||
changeLibrary(kind, id, shouldAdd) {
|
||||
if (shouldAdd) {
|
||||
app.addToLibrary(id);
|
||||
ipcRenderer.send('wsapi-change-library', kind, id, shouldAdd);
|
||||
} else {
|
||||
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
||||
|
||||
app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/?ids[${truekind}]=${id}`, {
|
||||
relate: "library",
|
||||
fields: "inLibrary"
|
||||
})
|
||||
.then(res => {
|
||||
res = res.data.data[0]
|
||||
if (res && res.relationships && res.relationships.library && res.relationships.library.data) {
|
||||
const item = res.relationships.library.data[0];
|
||||
|
||||
if (item) {
|
||||
app.removeFromLibrary(kind, item.id)
|
||||
}
|
||||
|
||||
ipcRenderer.send('wsapi-change-library', kind, id, shouldAdd);
|
||||
}
|
||||
});
|
||||
}
|
||||
attributes.status = isPlayingExport ? isPlayingExport : false;
|
||||
attributes.name = attributes.name ? attributes.name : "No Title Found";
|
||||
attributes.artwork = attributes.artwork ? attributes.artwork : { url: "" };
|
||||
attributes.artwork.url = attributes.artwork.url ? attributes.artwork.url : "";
|
||||
attributes.playParams = attributes.playParams ? attributes.playParams : { id: "no-id-found" };
|
||||
attributes.playParams.id = attributes.playParams.id ? attributes.playParams.id : "no-id-found";
|
||||
attributes.albumName = attributes.albumName ? attributes.albumName : "";
|
||||
attributes.artistName = attributes.artistName ? attributes.artistName : "";
|
||||
attributes.genreNames = attributes.genreNames ? attributes.genreNames : [];
|
||||
attributes.remainingTime = remainingTimeExport ? remainingTimeExport * 1000 : 0;
|
||||
attributes.durationInMillis = attributes.durationInMillis ? attributes.durationInMillis : 0;
|
||||
attributes.startTime = Date.now();
|
||||
attributes.endTime = attributes.endTime ? attributes.endTime : Date.now();
|
||||
attributes.volume = mk.volume;
|
||||
attributes.shuffleMode = mk.shuffleMode;
|
||||
attributes.repeatMode = mk.repeatMode;
|
||||
attributes.autoplayEnabled = mk.autoplayEnabled;
|
||||
return attributes;
|
||||
},
|
||||
moveQueueItem(oldPosition, newPosition) {
|
||||
MusicKit.getInstance().queue._queueItems.splice(newPosition, 0, MusicKit.getInstance().queue._queueItems.splice(oldPosition, 1)[0]);
|
||||
MusicKit.getInstance().queue._reindex();
|
||||
},
|
||||
setAutoplay(value) {
|
||||
MusicKit.getInstance().autoplayEnabled = value;
|
||||
},
|
||||
returnDynamic(data, type) {
|
||||
ipcRenderer.send("wsapi-returnDynamic", JSON.stringify(data), type);
|
||||
},
|
||||
musickitApi(method, id, params, library = false) {
|
||||
if (library) {
|
||||
MusicKit.getInstance()
|
||||
.api.library[method](id, params)
|
||||
.then((results) => {
|
||||
ipcRenderer.send("wsapi-returnMusicKitApi", JSON.stringify(results), method);
|
||||
});
|
||||
} else {
|
||||
MusicKit.getInstance()
|
||||
.api[method](id, params)
|
||||
.then((results) => {
|
||||
ipcRenderer.send("wsapi-returnMusicKitApi", JSON.stringify(results), method);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
getPlaybackState() {
|
||||
ipcRenderer.send("wsapi-updatePlaybackState", MusicKitInterop.getAttributes());
|
||||
},
|
||||
getLyrics() {
|
||||
ipcRenderer.send("wsapi-returnLyrics", JSON.stringify(app.lyrics));
|
||||
},
|
||||
getQueue() {
|
||||
ipcRenderer.send("wsapi-returnQueue", JSON.stringify(MusicKit.getInstance().queue));
|
||||
},
|
||||
playNext(type, id) {
|
||||
var request = {};
|
||||
request[type] = id;
|
||||
MusicKit.getInstance().playNext(request);
|
||||
},
|
||||
playLater(type, id) {
|
||||
var request = {};
|
||||
request[type] = id;
|
||||
MusicKit.getInstance().playLater(request);
|
||||
},
|
||||
love() {},
|
||||
playTrackById(id, kind = "song") {
|
||||
MusicKit.getInstance()
|
||||
.setQueue({ [kind]: id, parameters: { l: app.mklang } })
|
||||
.then(function (queue) {
|
||||
MusicKit.getInstance().play();
|
||||
});
|
||||
},
|
||||
quickPlay(term) {
|
||||
// Quick play by song name
|
||||
MusicKit.getInstance()
|
||||
.api.search(term, { limit: 2, types: "songs" })
|
||||
.then(function (data) {
|
||||
MusicKit.getInstance()
|
||||
.setQueue({
|
||||
song: data["songs"][0]["id"],
|
||||
parameters: { l: app.mklang },
|
||||
})
|
||||
.then(function (queue) {
|
||||
MusicKit.getInstance().play();
|
||||
});
|
||||
});
|
||||
},
|
||||
toggleShuffle() {
|
||||
MusicKit.getInstance().shuffleMode = MusicKit.getInstance().shuffleMode === 0 ? 1 : 0;
|
||||
},
|
||||
togglePlayPause() {
|
||||
app.mk.isPlaying ? app.mk.pause() : app.mk.play();
|
||||
},
|
||||
toggleRepeat() {
|
||||
if (MusicKit.getInstance().repeatMode == 0) {
|
||||
MusicKit.getInstance().repeatMode = 1;
|
||||
} else if (MusicKit.getInstance().repeatMode == 1) {
|
||||
MusicKit.getInstance().repeatMode = 2;
|
||||
} else {
|
||||
MusicKit.getInstance().repeatMode = 0;
|
||||
}
|
||||
},
|
||||
getmaxVolume() {
|
||||
ipcRenderer.send("wsapi-returnvolumeMax", JSON.stringify(app.cfg.audio.maxVolume));
|
||||
},
|
||||
getLibraryStatus(kind, id) {
|
||||
if (kind === undefined || id === "no-id-found") return;
|
||||
|
||||
export {wsapi}
|
||||
let truekind = !kind.endsWith("s") ? kind + "s" : kind;
|
||||
app.mk.api.v3
|
||||
.music(`/v1/catalog/${app.mk.storefrontId}/?ids[${truekind}]=${id}`, {
|
||||
relate: "library",
|
||||
fields: "inLibrary",
|
||||
})
|
||||
.then((data) => {
|
||||
const res = data.data.data[0];
|
||||
const inLibrary = res && res.attributes && res.attributes.inLibrary;
|
||||
|
||||
app.getRating({ type: truekind, id: id }).then((rating) => {
|
||||
ipcRenderer.send("wsapi-libraryStatus", inLibrary, rating);
|
||||
});
|
||||
});
|
||||
},
|
||||
rate(kind, id, rating) {
|
||||
if (kind === undefined || id === "no-id-found") return;
|
||||
|
||||
let truekind = !kind.endsWith("s") ? kind + "s" : kind;
|
||||
|
||||
if (rating === 0) {
|
||||
app.mk.api.v3
|
||||
.music(
|
||||
`/v1/me/ratings/${truekind}/${id}`,
|
||||
{},
|
||||
{
|
||||
fetchOptions: {
|
||||
method: "DELETE",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then(function () {
|
||||
ipcRenderer.send("wsapi-rate", kind, id, rating);
|
||||
});
|
||||
} else {
|
||||
app.mk.api.v3
|
||||
.music(
|
||||
`/v1/me/ratings/${truekind}/${id}`,
|
||||
{},
|
||||
{
|
||||
fetchOptions: {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
type: "rating",
|
||||
attributes: {
|
||||
value: rating,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
)
|
||||
.then(function () {
|
||||
ipcRenderer.send("wsapi-rate", kind, id, rating);
|
||||
});
|
||||
}
|
||||
},
|
||||
changeLibrary(kind, id, shouldAdd) {
|
||||
if (shouldAdd) {
|
||||
app.addToLibrary(id);
|
||||
ipcRenderer.send("wsapi-change-library", kind, id, shouldAdd);
|
||||
} else {
|
||||
let truekind = !kind.endsWith("s") ? kind + "s" : kind;
|
||||
|
||||
app.mk.api.v3
|
||||
.music(`/v1/catalog/${app.mk.storefrontId}/?ids[${truekind}]=${id}`, {
|
||||
relate: "library",
|
||||
fields: "inLibrary",
|
||||
})
|
||||
.then((res) => {
|
||||
res = res.data.data[0];
|
||||
if (res && res.relationships && res.relationships.library && res.relationships.library.data) {
|
||||
const item = res.relationships.library.data[0];
|
||||
|
||||
if (item) {
|
||||
app.removeFromLibrary(kind, item.id);
|
||||
}
|
||||
|
||||
ipcRenderer.send("wsapi-change-library", kind, id, shouldAdd);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export { wsapi };
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,2 +1,68 @@
|
|||
if(!self.define){let e,i={};const s=(s,r)=>(s=new URL(s+".js",r).href,i[s]||new Promise((i=>{if("document"in self){const e=document.createElement("script");e.src=s,e.onload=i,document.head.appendChild(e)}else e=s,importScripts(s),i()})).then((()=>{let e=i[s];if(!e)throw new Error(`Module ${s} didn’t register its module`);return e})));self.define=(r,c)=>{const n=e||("document"in self?document.currentScript.src:"")||location.href;if(i[n])return;let o={};const t=e=>s(e,n),a={module:{uri:n},exports:o,require:t};i[n]=Promise.all(r.map((e=>a[e]||t(e)))).then((e=>(c(...e),o)))}}define(["./workbox-962786f2"],(function(e){"use strict";self.addEventListener("message",(e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()})),e.precacheAndRoute([{url:"ameframework.css",revision:"4bcc8646bb5742638fad52b94e231601"},{url:"apple-hls.js",revision:"2b74055662676b0fcc2d4a4bf994a9dc"},{url:"hlscider.js",revision:"cf7f512e83e32694f2c94f904714fe4c"},{url:"index_old.html",revision:"c21f3e9c5b015599d3ab07639f64a7a8"},{url:"index.js",revision:"8591a69fc9c975a063eb264b7447f173"},{url:"less.js",revision:"b6e574e4d680686786a28e7e71a17bbc"},{url:"musickit.js",revision:"211d80891c3336c1795cb83df58d4b63"},{url:"sortable.min.js",revision:"5cbc31ebec32adf60e27b76418e79d93"},{url:"style-old.css",revision:"aea9ea49df13f2deee42b68654aeea06"},{url:"todo.js",revision:"18d49fabcb96de8bd11455877d8eacb6"},{url:"vue-observe-visibility.min.js",revision:"5a52e761f6aa71b4f65a7b458f698b95"},{url:"vue.js",revision:"0a9a4681294d8c5f476687eea6e74842"},{url:"vuedraggable.umd.min.js",revision:"9a84fec5263bb510cee88e1c3b9583cc"}],{ignoreURLParametersMatching:[/^utm_/,/^fbclid$/,/^X-Amz-Algorithm/,/^X-Amz-Date/,/^X-Amz-SignedHeaders/,/^X-Amz-Expires/,/^X-Amz-Credential/,/^X-Amz-Signature/]}),e.registerRoute(/\.(?:png|jpg|jpeg|svg|webp)$/,new e.CacheFirst({cacheName:"imageinternet",plugins:[]}),"GET"),e.registerRoute(/https:\/\/is[0-9]-ssl\.mzstatic\.com\/image+/,new e.CacheFirst,"GET"),e.registerRoute(/^https:\/\/store-\d{3}\.blobstore\.apple\.com\/.{65}\/image+/,new e.CacheFirst,"GET")}));
|
||||
if (!self.define) {
|
||||
let e,
|
||||
i = {};
|
||||
const s = (s, r) => (
|
||||
(s = new URL(s + ".js", r).href),
|
||||
i[s] ||
|
||||
new Promise((i) => {
|
||||
if ("document" in self) {
|
||||
const e = document.createElement("script");
|
||||
(e.src = s), (e.onload = i), document.head.appendChild(e);
|
||||
} else (e = s), importScripts(s), i();
|
||||
}).then(() => {
|
||||
let e = i[s];
|
||||
if (!e) throw new Error(`Module ${s} didn’t register its module`);
|
||||
return e;
|
||||
})
|
||||
);
|
||||
self.define = (r, c) => {
|
||||
const n = e || ("document" in self ? document.currentScript.src : "") || location.href;
|
||||
if (i[n]) return;
|
||||
let o = {};
|
||||
const t = (e) => s(e, n),
|
||||
a = { module: { uri: n }, exports: o, require: t };
|
||||
i[n] = Promise.all(r.map((e) => a[e] || t(e))).then((e) => (c(...e), o));
|
||||
};
|
||||
}
|
||||
define(["./workbox-962786f2"], function (e) {
|
||||
"use strict";
|
||||
self.addEventListener("message", (e) => {
|
||||
e.data && "SKIP_WAITING" === e.data.type && self.skipWaiting();
|
||||
}),
|
||||
e.precacheAndRoute(
|
||||
[
|
||||
{
|
||||
url: "ameframework.css",
|
||||
revision: "4bcc8646bb5742638fad52b94e231601",
|
||||
},
|
||||
{ url: "apple-hls.js", revision: "2b74055662676b0fcc2d4a4bf994a9dc" },
|
||||
{ url: "hlscider.js", revision: "cf7f512e83e32694f2c94f904714fe4c" },
|
||||
{ url: "index_old.html", revision: "c21f3e9c5b015599d3ab07639f64a7a8" },
|
||||
{ url: "index.js", revision: "8591a69fc9c975a063eb264b7447f173" },
|
||||
{ url: "less.js", revision: "b6e574e4d680686786a28e7e71a17bbc" },
|
||||
{ url: "musickit.js", revision: "211d80891c3336c1795cb83df58d4b63" },
|
||||
{
|
||||
url: "sortable.min.js",
|
||||
revision: "5cbc31ebec32adf60e27b76418e79d93",
|
||||
},
|
||||
{ url: "style-old.css", revision: "aea9ea49df13f2deee42b68654aeea06" },
|
||||
{ url: "todo.js", revision: "18d49fabcb96de8bd11455877d8eacb6" },
|
||||
{
|
||||
url: "vue-observe-visibility.min.js",
|
||||
revision: "5a52e761f6aa71b4f65a7b458f698b95",
|
||||
},
|
||||
{ url: "vue.js", revision: "0a9a4681294d8c5f476687eea6e74842" },
|
||||
{
|
||||
url: "vuedraggable.umd.min.js",
|
||||
revision: "9a84fec5263bb510cee88e1c3b9583cc",
|
||||
},
|
||||
],
|
||||
{
|
||||
ignoreURLParametersMatching: [/^utm_/, /^fbclid$/, /^X-Amz-Algorithm/, /^X-Amz-Date/, /^X-Amz-SignedHeaders/, /^X-Amz-Expires/, /^X-Amz-Credential/, /^X-Amz-Signature/],
|
||||
}
|
||||
),
|
||||
e.registerRoute(/\.(?:png|jpg|jpeg|svg|webp)$/, new e.CacheFirst({ cacheName: "imageinternet", plugins: [] }), "GET"),
|
||||
e.registerRoute(/https:\/\/is[0-9]-ssl\.mzstatic\.com\/image+/, new e.CacheFirst(), "GET"),
|
||||
e.registerRoute(/^https:\/\/store-\d{3}\.blobstore\.apple\.com\/.{65}\/image+/, new e.CacheFirst(), "GET");
|
||||
});
|
||||
//# sourceMappingURL=sw.js.map
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#app {
|
||||
--color1: #111;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
// Default theme
|
||||
// Default theme
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
body.notransparency::before {
|
||||
display: block;
|
||||
}
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -1,41 +1,40 @@
|
|||
&:not(.modular-fs) {
|
||||
.app-drawer {
|
||||
border-radius: 0px;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
box-shadow: unset;
|
||||
border-left: 1px solid var(--color2);
|
||||
background: var(--color1);
|
||||
margin-right: 0px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.drawertransition-enter-active,
|
||||
.drawertransition-leave-active {
|
||||
transition: margin 0.25s var(--appleEase), opacity 0.25s var(--appleEase);
|
||||
}
|
||||
|
||||
.drawertransition-enter,
|
||||
.drawertransition-leave-to {
|
||||
margin-right: -300px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1120px) {
|
||||
.app-drawer {
|
||||
border-radius: 0px;
|
||||
top : 0;
|
||||
right : 0;
|
||||
height : 100%;
|
||||
box-shadow : unset;
|
||||
border-left : 1px solid var(--color2);
|
||||
background : var(--color1);
|
||||
margin-right : 0px;
|
||||
position : relative;
|
||||
margin-right: 0px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.drawertransition-enter-active,
|
||||
.drawertransition-leave-active {
|
||||
transition: margin .25s var(--appleEase), opacity .25s var(--appleEase);
|
||||
transition: right 0.25s var(--appleEase), opacity 0.25s var(--appleEase);
|
||||
}
|
||||
|
||||
.drawertransition-enter,
|
||||
.drawertransition-leave-to {
|
||||
margin-right: -300px;
|
||||
right: -300px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1120px) {
|
||||
.app-drawer {
|
||||
margin-right: 0px;
|
||||
position : absolute;
|
||||
}
|
||||
|
||||
.drawertransition-enter-active,
|
||||
.drawertransition-leave-active {
|
||||
transition: right .25s var(--appleEase), opacity .25s var(--appleEase);
|
||||
}
|
||||
|
||||
.drawertransition-enter,
|
||||
.drawertransition-leave-to {
|
||||
right: -300px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,141 +1,139 @@
|
|||
body {
|
||||
--ciderShadow-Generic : var(--mediaItemShadow);
|
||||
--mediaItemShadow-Shadow : var(--mediaItemShadow);
|
||||
--mediaItemShadow-ShadowSubtle: var(--mediaItemShadow);
|
||||
--ciderShadow-Generic: var(--mediaItemShadow);
|
||||
--mediaItemShadow-Shadow: var(--mediaItemShadow);
|
||||
--mediaItemShadow-ShadowSubtle: var(--mediaItemShadow);
|
||||
}
|
||||
|
||||
.bg-artwork-container {
|
||||
display : none;
|
||||
animation: none !important;
|
||||
display: none;
|
||||
animation: none !important;
|
||||
|
||||
.bg-artwork {
|
||||
animation: none !important;
|
||||
}
|
||||
.bg-artwork {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.app-chrome:not(.chrome-bottom) {
|
||||
backdrop-filter: unset;
|
||||
background-color: var(--baseColor);
|
||||
backdrop-filter: unset;
|
||||
background-color: var(--baseColor);
|
||||
}
|
||||
|
||||
.menu-panel .menu-panel-body {
|
||||
background: rgb(30 30 30);
|
||||
background: rgb(30 30 30);
|
||||
}
|
||||
.menu-panel .menu-panel-body .menu-option::before {
|
||||
transition: unset!important;
|
||||
transition: unset !important;
|
||||
}
|
||||
|
||||
#app.twopanel .app-chrome:not(.chrome-bottom) .app-chrome--center .top-nav-group .app-sidebar-item:before {
|
||||
transition: unset!important;
|
||||
transition: unset !important;
|
||||
}
|
||||
|
||||
.playback-button:before, .playback-button--small:before {
|
||||
transition: unset!important;
|
||||
.playback-button:before,
|
||||
.playback-button--small:before {
|
||||
transition: unset !important;
|
||||
}
|
||||
|
||||
.floating-header {
|
||||
backdrop-filter: unset!important;
|
||||
background: rgb(0 0 0 / 80%)!important;
|
||||
backdrop-filter: unset !important;
|
||||
background: rgb(0 0 0 / 80%) !important;
|
||||
}
|
||||
|
||||
.replaycard-enter-active,
|
||||
.replaycard-leave-active {
|
||||
transition: unset;
|
||||
transition: unset;
|
||||
}
|
||||
|
||||
.replaycard-enter,
|
||||
.replaycard-leave-to {
|
||||
opacity : 0;
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
.modal-enter-active,
|
||||
.modal-leave-active {
|
||||
transition: unset;
|
||||
transition: unset;
|
||||
}
|
||||
|
||||
.modal-enter,
|
||||
.modal-leave-to {
|
||||
opacity : 0;
|
||||
transform: scale(1.10);
|
||||
opacity: 0;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.wpfade-enter-active,
|
||||
.wpfade-leave-active {
|
||||
transition: opacity .1s var(--appleEase);
|
||||
transition: opacity 0.1s var(--appleEase);
|
||||
}
|
||||
|
||||
.wpfade-enter,
|
||||
.wpfade-leave-to {
|
||||
opacity: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.wpfade_transform-enter-active,
|
||||
.wpfade_transform-leave-active {
|
||||
transition : unset;
|
||||
will-change: unset;
|
||||
transition: unset;
|
||||
will-change: unset;
|
||||
}
|
||||
|
||||
.wpfade_transform-enter {
|
||||
opacity : 0;
|
||||
transform : unset;
|
||||
will-change: unset;
|
||||
opacity: 0;
|
||||
transform: unset;
|
||||
will-change: unset;
|
||||
}
|
||||
|
||||
.wpfade_transform-leave-to {
|
||||
opacity : 0;
|
||||
transform : unset;
|
||||
will-change: unset;
|
||||
opacity: 0;
|
||||
transform: unset;
|
||||
will-change: unset;
|
||||
}
|
||||
|
||||
|
||||
.wpfade_transform_backwards-enter-active,
|
||||
.wpfade_transform_backwards-leave-active {
|
||||
transition: unset;
|
||||
transition: unset;
|
||||
}
|
||||
|
||||
.wpfade_transform_backwards-enter {
|
||||
opacity : 0;
|
||||
transform : unset;
|
||||
will-change: unset;
|
||||
opacity: 0;
|
||||
transform: unset;
|
||||
will-change: unset;
|
||||
}
|
||||
|
||||
.wpfade_transform_backwards-leave-to {
|
||||
opacity : 0;
|
||||
transform : unset;
|
||||
will-change: unset;
|
||||
opacity: 0;
|
||||
transform: unset;
|
||||
will-change: unset;
|
||||
}
|
||||
|
||||
.fabfade-enter-active,
|
||||
.fabfade-leave-active {
|
||||
transition: unset;
|
||||
transition: unset;
|
||||
}
|
||||
|
||||
.fabfade-enter,
|
||||
.fabfade-leave-to {
|
||||
opacity : 0;
|
||||
transform: scale(0.5);
|
||||
opacity: 0;
|
||||
transform: scale(0.5);
|
||||
}
|
||||
|
||||
.fsModeSwitch-enter-active,
|
||||
.fsModeSwitch-leave-active {
|
||||
transition: unset;
|
||||
transition: unset;
|
||||
}
|
||||
|
||||
.fsModeSwitch-enter,
|
||||
.fsModeSwitch-leave-to {
|
||||
transform: scale(1.10);
|
||||
opacity : 0;
|
||||
transform: scale(1.1);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
.drawertransition-enter-active,
|
||||
.drawertransition-leave-active {
|
||||
transition: unset;
|
||||
transition: unset;
|
||||
}
|
||||
|
||||
.drawertransition-enter,
|
||||
.drawertransition-leave-to {
|
||||
right: -300px;
|
||||
}
|
||||
right: -300px;
|
||||
}
|
||||
|
|
|
@ -1,49 +1,47 @@
|
|||
@panelColorsFallback: rgb(30 30 30);
|
||||
@panelColors : rgb(30 30 30 / 45%);
|
||||
@panelColors: rgb(30 30 30 / 45%);
|
||||
|
||||
.menu-panel {
|
||||
.menu-panel-body {
|
||||
background-color: @panelColors;
|
||||
backdrop-filter : blur(32px) saturate(180%);
|
||||
.menu-panel-body {
|
||||
background-color: @panelColors;
|
||||
backdrop-filter: blur(32px) saturate(180%);
|
||||
|
||||
&.menu-panel-body-down {
|
||||
animation: menuInDown .10s var(--appleEase);
|
||||
}
|
||||
|
||||
&.menu-panel-body-up {
|
||||
animation: menuInUp .10s var(--appleEase);
|
||||
}
|
||||
|
||||
&.menu-panel-body-down {
|
||||
animation: menuInDown 0.1s var(--appleEase);
|
||||
}
|
||||
|
||||
@keyframes menuInUp {
|
||||
0% {
|
||||
opacity : 0;
|
||||
transform : translateY(-10px) translate3d(0,0,0);
|
||||
background: @panelColorsFallback;
|
||||
}
|
||||
&.menu-panel-body-up {
|
||||
animation: menuInUp 0.1s var(--appleEase);
|
||||
}
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity : 1;
|
||||
transform : translateY(0);
|
||||
background: @panelColors;
|
||||
}
|
||||
@keyframes menuInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px) translate3d(0, 0, 0);
|
||||
background: @panelColorsFallback;
|
||||
}
|
||||
|
||||
@keyframes menuInDown {
|
||||
0% {
|
||||
opacity : 0;
|
||||
transform : translateY(10px) translate3d(0,0,0);
|
||||
background: @panelColorsFallback;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
background: @panelColors;
|
||||
}
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity : 1;
|
||||
transform : translateY(0);
|
||||
background: @panelColors;
|
||||
}
|
||||
@keyframes menuInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(10px) translate3d(0, 0, 0);
|
||||
background: @panelColorsFallback;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
background: @panelColors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cd-mediaitem-square {
|
||||
|
@ -55,47 +53,46 @@
|
|||
}
|
||||
|
||||
.cd-mediaitem-square:not(.mediaitem-card) {
|
||||
transition : transform .2s var(--appleEase);
|
||||
transition-delay: .1s;
|
||||
transition: transform 0.2s var(--appleEase);
|
||||
transition-delay: 0.1s;
|
||||
|
||||
.artwork-container {
|
||||
}
|
||||
|
||||
.artwork-container {}
|
||||
.info-rect {
|
||||
}
|
||||
|
||||
.info-rect {}
|
||||
.artwork-container,
|
||||
.info-rect {
|
||||
transition: transform 0.22s var(--appleEase);
|
||||
transition-delay: 0.05s;
|
||||
}
|
||||
|
||||
.artwork-container,
|
||||
.info-rect {
|
||||
transition : transform .22s var(--appleEase);
|
||||
transition-delay: .05s;
|
||||
}
|
||||
.artwork-container {
|
||||
transform: scale(0.962) translateZ(0);
|
||||
transition: transform 0.1s var(--appleEase);
|
||||
transition-delay: 0s;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.artwork-container {
|
||||
transform : scale(0.962) translateZ(0);
|
||||
transition : transform .1s var(--appleEase);
|
||||
transform: scale(1);
|
||||
transition: transform 0.1s var(--appleEase);
|
||||
transition-delay: 0s;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.artwork-container {
|
||||
transform : scale(1.0);
|
||||
transition : transform .1s var(--appleEase);
|
||||
transition-delay: 0s;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
|
||||
.info-rect {
|
||||
z-index : 1;
|
||||
transition : transform .1s var(--appleEase);
|
||||
transition-delay: 0s;
|
||||
transform : translateY(8px) translate3d(0,0,0);
|
||||
}
|
||||
.info-rect {
|
||||
z-index: 1;
|
||||
transition: transform 0.1s var(--appleEase);
|
||||
transition-delay: 0s;
|
||||
transform: translateY(8px) translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
|
||||
}
|
||||
&:active {
|
||||
}
|
||||
}
|
||||
|
||||
.wpfade_transform-enter-active,
|
||||
|
@ -107,16 +104,15 @@
|
|||
|
||||
.wpfade_transform-enter {
|
||||
opacity: 0;
|
||||
transform: translateX(50%) translate3d(0,0,0);
|
||||
transform: translateX(50%) translate3d(0, 0, 0);
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
.wpfade_transform-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) translate3d(0,0,0);
|
||||
transform: translateX(-50%) translate3d(0, 0, 0);
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
|
||||
|
||||
.wpfade_transform_backwards-enter-active,
|
||||
.wpfade_transform_backwards-leave-active {
|
||||
--transitionTime: 0.2s;
|
||||
|
@ -125,11 +121,11 @@
|
|||
|
||||
.wpfade_transform_backwards-enter {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) translate3d(0,0,0);
|
||||
transform: translateX(-50%) translate3d(0, 0, 0);
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
.wpfade_transform_backwards-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(50%) translate3d(0,0,0);
|
||||
transform: translateX(50%) translate3d(0, 0, 0);
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,180 +1,193 @@
|
|||
// Apple Music Listen Now Page
|
||||
// URL : https://amp-api.music.apple.com/v1/me/recommendations?timezone=+00:00
|
||||
// &with=friendsMix,library,social&art[social-profiles:url]=c
|
||||
// &name=listen-now&art[url]=c,f&omit[resource]=autos
|
||||
// &relate[editorial-items]=contents
|
||||
// &extend=editorialCard,editorialVideo
|
||||
// &extend[albums]=artistUrl
|
||||
// &extend[library-albums]=artistUrl
|
||||
// &extend[playlists]=artistNames,editorialArtwork
|
||||
// &extend[library-playlists]=artistNames,editorialArtwork
|
||||
// &extend[social-profiles]=topGenreNames&include[albums]=artists
|
||||
// &include[songs]=artists&include[music-videos]=artists
|
||||
// &fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url
|
||||
// &fields[artists]=name,url&extend[stations]=airDate,supportsAirTimeUpdates&meta[stations]=inflectionPoints
|
||||
// &types=artists,albums,editorial-items,library-albums,library-playlists,music-movies,music-videos,playlists,stations,uploaded-audios,uploaded-videos,activities,apple-curators,curators,tv-shows,social-profiles,social-upsells
|
||||
// &l=en-gb&platform=web
|
||||
// &with=friendsMix,library,social&art[social-profiles:url]=c
|
||||
// &name=listen-now&art[url]=c,f&omit[resource]=autos
|
||||
// &relate[editorial-items]=contents
|
||||
// &extend=editorialCard,editorialVideo
|
||||
// &extend[albums]=artistUrl
|
||||
// &extend[library-albums]=artistUrl
|
||||
// &extend[playlists]=artistNames,editorialArtwork
|
||||
// &extend[library-playlists]=artistNames,editorialArtwork
|
||||
// &extend[social-profiles]=topGenreNames&include[albums]=artists
|
||||
// &include[songs]=artists&include[music-videos]=artists
|
||||
// &fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url
|
||||
// &fields[artists]=name,url&extend[stations]=airDate,supportsAirTimeUpdates&meta[stations]=inflectionPoints
|
||||
// &types=artists,albums,editorial-items,library-albums,library-playlists,music-movies,music-videos,playlists,stations,uploaded-audios,uploaded-videos,activities,apple-curators,curators,tv-shows,social-profiles,social-upsells
|
||||
// &l=en-gb&platform=web
|
||||
|
||||
await app.mk.api.personalRecommendations("",
|
||||
{
|
||||
name: "listen-now",
|
||||
with: "friendsMix,library,social",
|
||||
"art[social-profiles:url]":"c",
|
||||
"art[url]": "c,f",
|
||||
"omit[resource]": "autos",
|
||||
"relate[editorial-items]": "contents",
|
||||
extend: ["editorialCard", "editorialVideo"],
|
||||
"extend[albums]": ["artistUrl"],
|
||||
"extend[library-albums]": ["artistUrl"],
|
||||
"extend[playlists]": ["artistNames", "editorialArtwork"],
|
||||
"extend[library-playlists]": ["artistNames", "editorialArtwork"],
|
||||
"extend[social-profiles]": "topGenreNames",
|
||||
"include[albums]": "artists",
|
||||
"include[songs]": "artists",
|
||||
"include[music-videos]": "artists",
|
||||
"fields[albums]": ["artistName", "artistUrl", "artwork", "contentRating", "editorialArtwork", "editorialVideo", "name", "playParams", "releaseDate", "url"],
|
||||
"fields[artists]": ["name", "url"],
|
||||
"extend[stations]": ["airDate", "supportsAirTimeUpdates"],
|
||||
"meta[stations]": "inflectionPoints",
|
||||
types: "artists,albums,editorial-items,library-albums,library-playlists,music-movies,music-videos,playlists,stations,uploaded-audios,uploaded-videos,activities,apple-curators,curators,tv-shows,social-profiles,social-upsells",
|
||||
l:"en-gb",
|
||||
platform:"web"
|
||||
},
|
||||
{
|
||||
includeResponseMeta: !0,
|
||||
reload: !0
|
||||
});
|
||||
await app.mk.api.personalRecommendations(
|
||||
"",
|
||||
{
|
||||
name: "listen-now",
|
||||
with: "friendsMix,library,social",
|
||||
"art[social-profiles:url]": "c",
|
||||
"art[url]": "c,f",
|
||||
"omit[resource]": "autos",
|
||||
"relate[editorial-items]": "contents",
|
||||
extend: ["editorialCard", "editorialVideo"],
|
||||
"extend[albums]": ["artistUrl"],
|
||||
"extend[library-albums]": ["artistUrl"],
|
||||
"extend[playlists]": ["artistNames", "editorialArtwork"],
|
||||
"extend[library-playlists]": ["artistNames", "editorialArtwork"],
|
||||
"extend[social-profiles]": "topGenreNames",
|
||||
"include[albums]": "artists",
|
||||
"include[songs]": "artists",
|
||||
"include[music-videos]": "artists",
|
||||
"fields[albums]": ["artistName", "artistUrl", "artwork", "contentRating", "editorialArtwork", "editorialVideo", "name", "playParams", "releaseDate", "url"],
|
||||
"fields[artists]": ["name", "url"],
|
||||
"extend[stations]": ["airDate", "supportsAirTimeUpdates"],
|
||||
"meta[stations]": "inflectionPoints",
|
||||
types: "artists,albums,editorial-items,library-albums,library-playlists,music-movies,music-videos,playlists,stations,uploaded-audios,uploaded-videos,activities,apple-curators,curators,tv-shows,social-profiles,social-upsells",
|
||||
l: "en-gb",
|
||||
platform: "web",
|
||||
},
|
||||
{
|
||||
includeResponseMeta: !0,
|
||||
reload: !0,
|
||||
}
|
||||
);
|
||||
|
||||
// Browse page
|
||||
await app.mk.api.groupings("",
|
||||
{
|
||||
platform: "web",
|
||||
name: "music",
|
||||
l: "en-gb",
|
||||
"omit[resource:artists]": "relationships",
|
||||
"include[albums]": "artists",
|
||||
"include[songs]": "artists",
|
||||
"include[music-videos]": "artists",
|
||||
extend: "editorialArtwork,artistUrl",
|
||||
"fields[artists]": "name,url,artwork,editorialArtwork,genreNames,editorialNotes",
|
||||
"art[url]": "f"
|
||||
});
|
||||
await app.mk.api.groupings("", {
|
||||
platform: "web",
|
||||
name: "music",
|
||||
l: "en-gb",
|
||||
"omit[resource:artists]": "relationships",
|
||||
"include[albums]": "artists",
|
||||
"include[songs]": "artists",
|
||||
"include[music-videos]": "artists",
|
||||
extend: "editorialArtwork,artistUrl",
|
||||
"fields[artists]": "name,url,artwork,editorialArtwork,genreNames,editorialNotes",
|
||||
"art[url]": "f",
|
||||
});
|
||||
|
||||
// Radio page
|
||||
await app.mk.api.recentRadioStations("",
|
||||
{l: "en-gb",
|
||||
"platform": "web",
|
||||
"art[url]": "f"});
|
||||
await app.mk.api.recentRadioStations("", {
|
||||
l: "en-gb",
|
||||
platform: "web",
|
||||
"art[url]": "f",
|
||||
});
|
||||
|
||||
// Recently Added
|
||||
await app.mk.api.library.recentlyAdded({
|
||||
"platform": "web",
|
||||
await app.mk.api.library.recentlyAdded(
|
||||
{
|
||||
platform: "web",
|
||||
include: {
|
||||
"library-albums": ["artists"],
|
||||
"library-artists": ["catalog"]
|
||||
"library-albums": ["artists"],
|
||||
"library-artists": ["catalog"],
|
||||
},
|
||||
fields: {
|
||||
artists: ["url"],
|
||||
albums: "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url"
|
||||
artists: ["url"],
|
||||
albums: "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url",
|
||||
},
|
||||
includeOnly: ["catalog", "artists"],
|
||||
limit: 25
|
||||
}, {
|
||||
limit: 25,
|
||||
},
|
||||
{
|
||||
reload: !0,
|
||||
includePagination: !0
|
||||
})
|
||||
includePagination: !0,
|
||||
}
|
||||
);
|
||||
|
||||
// Songs
|
||||
await app.mk.api.library.songs({limit: 100}).then((data)=>{
|
||||
console.log(data)
|
||||
})
|
||||
// Songs
|
||||
await app.mk.api.library.songs({ limit: 100 }).then((data) => {
|
||||
console.log(data);
|
||||
});
|
||||
|
||||
// Artists
|
||||
await app.mk.api.library.artists({limit: 100}).then((data)=>{
|
||||
console.log(data)
|
||||
})
|
||||
// Artists
|
||||
await app.mk.api.library.artists({ limit: 100 }).then((data) => {
|
||||
console.log(data);
|
||||
});
|
||||
|
||||
// Artists
|
||||
await app.mk.api.library.albums({limit: 100}).then((data)=>{
|
||||
console.log(data)
|
||||
})
|
||||
// Artists
|
||||
await app.mk.api.library.albums({ limit: 100 }).then((data) => {
|
||||
console.log(data);
|
||||
});
|
||||
|
||||
// Albums
|
||||
// does not like limit = 100 for some reason
|
||||
await app.mk.api.library.albums({limit: 50}).then((data)=>{
|
||||
console.log(data)
|
||||
})
|
||||
await app.mk.api.library.albums({ limit: 50 }).then((data) => {
|
||||
console.log(data);
|
||||
});
|
||||
|
||||
// Made For You
|
||||
app.mk.api.recommendations("",{extend: "editorialArtwork,artistUrl"})
|
||||
app.mk.api.recommendations("", { extend: "editorialArtwork,artistUrl" });
|
||||
|
||||
// Library with library length
|
||||
await app.mk.api.library.songs("", {limit: 100}, {includeResponseMeta: !0}).then((data)=>{
|
||||
console.log(data)
|
||||
})
|
||||
await app.mk.api.library.songs("", { limit: 100 }, { includeResponseMeta: !0 }).then((data) => {
|
||||
console.log(data);
|
||||
});
|
||||
|
||||
// Artist View Top Songs
|
||||
app.mk.api.artistView("325096253", "top-songs", {}, {view: "top-songs", includeResponseMeta: !0})
|
||||
app.mk.api.artistView("325096253", "top-songs", {}, { view: "top-songs", includeResponseMeta: !0 });
|
||||
|
||||
// Artist Page Data
|
||||
app.mkapi("artists", false, "412778295", {
|
||||
"views": "featured-release,full-albums,appears-on-albums,featured-albums,featured-on-albums,singles,compilation-albums,live-albums,latest-release,top-music-videos,similar-artists,top-songs,playlists,more-to-hear,more-to-see",
|
||||
"extend": "artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero",
|
||||
"extend[playlists]": "trackCount",
|
||||
"omit[resource:songs]": "relationships",
|
||||
"fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url,trackCount",
|
||||
"limit[artists:top-songs]": 20,
|
||||
"art[url]": "f"
|
||||
}, {includeResponseMeta: !0}).then((data)=>{
|
||||
console.log(data)
|
||||
})
|
||||
app
|
||||
.mkapi(
|
||||
"artists",
|
||||
false,
|
||||
"412778295",
|
||||
{
|
||||
views: "featured-release,full-albums,appears-on-albums,featured-albums,featured-on-albums,singles,compilation-albums,live-albums,latest-release,top-music-videos,similar-artists,top-songs,playlists,more-to-hear,more-to-see",
|
||||
extend: "artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero",
|
||||
"extend[playlists]": "trackCount",
|
||||
"omit[resource:songs]": "relationships",
|
||||
"fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url,trackCount",
|
||||
"limit[artists:top-songs]": 20,
|
||||
"art[url]": "f",
|
||||
},
|
||||
{ includeResponseMeta: !0 }
|
||||
)
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
});
|
||||
|
||||
// download entire library
|
||||
var library = []
|
||||
var library = [];
|
||||
var downloaded = null;
|
||||
function downloadChunk () {
|
||||
if (downloaded == null) {
|
||||
app.mk.api.library.songs("", {limit: 100}, {includeResponseMeta: !0}).then((response)=>{
|
||||
processChunk(response)
|
||||
})
|
||||
} else {
|
||||
downloaded.next("", {limit: 100}, {includeResponseMeta: !0}).then((response)=>{
|
||||
processChunk(response)
|
||||
})
|
||||
}
|
||||
function downloadChunk() {
|
||||
if (downloaded == null) {
|
||||
app.mk.api.library.songs("", { limit: 100 }, { includeResponseMeta: !0 }).then((response) => {
|
||||
processChunk(response);
|
||||
});
|
||||
} else {
|
||||
downloaded.next("", { limit: 100 }, { includeResponseMeta: !0 }).then((response) => {
|
||||
processChunk(response);
|
||||
});
|
||||
}
|
||||
}
|
||||
function processChunk (response) {
|
||||
downloaded = response
|
||||
library = library.concat(downloaded.data)
|
||||
if (downloaded.meta.total > library.length) {
|
||||
console.log(`downloading next chunk - ${library.length} songs so far`)
|
||||
downloadChunk()
|
||||
} else {
|
||||
console.log(library)
|
||||
}
|
||||
function processChunk(response) {
|
||||
downloaded = response;
|
||||
library = library.concat(downloaded.data);
|
||||
if (downloaded.meta.total > library.length) {
|
||||
console.log(`downloading next chunk - ${library.length} songs so far`);
|
||||
downloadChunk();
|
||||
} else {
|
||||
console.log(library);
|
||||
}
|
||||
}
|
||||
|
||||
//Some Available Functions from MusicKit
|
||||
// recentPlayed() -> recently played songs ?
|
||||
|
||||
// create Artist / Song/ Album stations:
|
||||
app.mk.setStationQueue({artist:"1258279972"})
|
||||
app.mk.setStationQueue({song:"1437308307"}) // yes the song id here can be the albumId, but just keep using the song:
|
||||
app.mk.setStationQueue({ artist: "1258279972" });
|
||||
app.mk.setStationQueue({ song: "1437308307" }); // yes the song id here can be the albumId, but just keep using the song:
|
||||
|
||||
// Sorting Playlists, send an array of tracks in the format below
|
||||
// playlist must be fully recursively downloaded first before sorting
|
||||
|
||||
app.mk.api.library.putPlaylistTracklisting(app.showingPlaylist.attributes.playParams.id, [
|
||||
{
|
||||
"id": relationships.tracks.data[X].id,
|
||||
"type": relationships.tracks.data[X].type
|
||||
},
|
||||
{
|
||||
"id": relationships.tracks.data[X].id,
|
||||
"type": relationships.tracks.data[X].type
|
||||
},
|
||||
{
|
||||
"id": relationships.tracks.data[X].id,
|
||||
"type": relationships.tracks.data[X].type
|
||||
},
|
||||
])
|
||||
{
|
||||
id: relationships.tracks.data[X].id,
|
||||
type: relationships.tracks.data[X].type,
|
||||
},
|
||||
{
|
||||
id: relationships.tracks.data[X].id,
|
||||
type: relationships.tracks.data[X].type,
|
||||
},
|
||||
{
|
||||
id: relationships.tracks.data[X].id,
|
||||
type: relationships.tracks.data[X].type,
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -1,136 +1,136 @@
|
|||
<div class="app-navigation" v-cloak>
|
||||
<transition name="wpfade">
|
||||
<div class="usermenu-container" v-if="chrome.menuOpened">
|
||||
<div class="usermenu-body">
|
||||
<button
|
||||
class="app-sidebar-button"
|
||||
style="width: 100%"
|
||||
@click="appRoute('apple-account-settings')"
|
||||
>
|
||||
<img
|
||||
class="sidebar-user-icon"
|
||||
loading="lazy"
|
||||
:src="getMediaItemArtwork(chrome.hideUserInfo ? './assets/logocut.png' : (chrome.userinfo.attributes['artwork'] ? chrome.userinfo.attributes['artwork']['url'] : ''), 26)"
|
||||
/>
|
||||
<div class="usermenu-container" v-if="chrome.menuOpened">
|
||||
<div class="usermenu-body">
|
||||
<button
|
||||
class="app-sidebar-button"
|
||||
style="width: 100%"
|
||||
@click="appRoute('apple-account-settings')"
|
||||
>
|
||||
<img
|
||||
class="sidebar-user-icon"
|
||||
loading="lazy"
|
||||
:src="getMediaItemArtwork(chrome.hideUserInfo ? './assets/logocut.png' : (chrome.userinfo.attributes['artwork'] ? chrome.userinfo.attributes['artwork']['url'] : ''), 26)"
|
||||
/>
|
||||
|
||||
<div class="sidebar-user-text" v-if="!chrome.hideUserInfo">
|
||||
<template v-if="chrome.userinfo.id || mk.isAuthorized">
|
||||
<div class="fullname text-overflow-elipsis">
|
||||
{{
|
||||
chrome.userinfo != null &&
|
||||
chrome.userinfo.attributes != null
|
||||
? chrome.userinfo.attributes.name ?? ""
|
||||
: ""
|
||||
}}
|
||||
</div>
|
||||
<div class="handle-text text-overflow-elipsis">
|
||||
{{
|
||||
chrome.userinfo != null &&
|
||||
chrome.userinfo.attributes != null
|
||||
? chrome.userinfo.attributes.handle ?? ""
|
||||
: ""
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div @click="mk.authorize()">
|
||||
{{ $root.getLz("term.login") }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="sidebar-user-text" v-else>
|
||||
{{ $root.getLz("app.name") }}
|
||||
</div>
|
||||
</button>
|
||||
<!-- Use 20px SVG for usermenu icon -->
|
||||
<button
|
||||
class="usermenu-item"
|
||||
v-if="cfg.general.privateEnabled"
|
||||
@click="cfg.general.privateEnabled = false"
|
||||
>
|
||||
<div class="sidebar-user-text" v-if="!chrome.hideUserInfo">
|
||||
<template v-if="chrome.userinfo.id || mk.isAuthorized">
|
||||
<div class="fullname text-overflow-elipsis">
|
||||
{{
|
||||
chrome.userinfo != null &&
|
||||
chrome.userinfo.attributes != null
|
||||
? chrome.userinfo.attributes.name ?? ""
|
||||
: ""
|
||||
}}
|
||||
</div>
|
||||
<div class="handle-text text-overflow-elipsis">
|
||||
{{
|
||||
chrome.userinfo != null &&
|
||||
chrome.userinfo.attributes != null
|
||||
? chrome.userinfo.attributes.handle ?? ""
|
||||
: ""
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div @click="mk.authorize()">
|
||||
{{ $root.getLz("term.login") }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="sidebar-user-text" v-else>
|
||||
{{ $root.getLz("app.name") }}
|
||||
</div>
|
||||
</button>
|
||||
<!-- Use 20px SVG for usermenu icon -->
|
||||
<button
|
||||
class="usermenu-item"
|
||||
v-if="cfg.general.privateEnabled"
|
||||
@click="cfg.general.privateEnabled = false"
|
||||
>
|
||||
<span class="usermenu-item-icon">
|
||||
<%- include("../svg/x.svg") %>
|
||||
</span>
|
||||
<span class="usermenu-item-name">{{
|
||||
<span class="usermenu-item-name">{{
|
||||
$root.getLz("term.disablePrivateSession")
|
||||
}}</span>
|
||||
</button>
|
||||
<button class="usermenu-item" @click="appRoute('remote-pair')">
|
||||
</button>
|
||||
<button class="usermenu-item" @click="appRoute('remote-pair')">
|
||||
<span class="usermenu-item-icon">
|
||||
<%- include("../svg/smartphone.svg") %>
|
||||
</span>
|
||||
<span class="usermenu-item-name">{{
|
||||
<span class="usermenu-item-name">{{
|
||||
$root.getLz("action.showWebRemoteQR")
|
||||
}}</span>
|
||||
</button>
|
||||
<button
|
||||
class="usermenu-item"
|
||||
@click="modals.castMenu = true"
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
class="usermenu-item"
|
||||
@click="modals.castMenu = true"
|
||||
>
|
||||
<span class="usermenu-item-icon">
|
||||
<%- include("../svg/cast.svg") %>
|
||||
</span>
|
||||
<span class="usermenu-item-name">{{
|
||||
<span class="usermenu-item-name">{{
|
||||
$root.getLz("term.cast")
|
||||
}}</span>
|
||||
</button>
|
||||
<button
|
||||
class="usermenu-item"
|
||||
@click="modals.audioSettings = true"
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
class="usermenu-item"
|
||||
@click="modals.audioSettings = true"
|
||||
>
|
||||
<span class="usermenu-item-icon">
|
||||
<%- include("../svg/headphones.svg") %>
|
||||
</span>
|
||||
<span class="usermenu-item-name">{{
|
||||
<span class="usermenu-item-name">{{
|
||||
$root.getLz("term.audioSettings")
|
||||
}}</span>
|
||||
</button>
|
||||
<button
|
||||
class="usermenu-item"
|
||||
v-if="pluginInstalled"
|
||||
@click="modals.pluginMenu = true"
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
class="usermenu-item"
|
||||
v-if="pluginInstalled"
|
||||
@click="modals.pluginMenu = true"
|
||||
>
|
||||
<span class="usermenu-item-icon">
|
||||
<%- include("../svg/grid.svg") %>
|
||||
</span>
|
||||
<span class="usermenu-item-name">{{
|
||||
<span class="usermenu-item-name">{{
|
||||
$root.getLz("term.plugin")
|
||||
}}</span>
|
||||
</button>
|
||||
<button class="usermenu-item" @click="appRoute('about')">
|
||||
</button>
|
||||
<button class="usermenu-item" @click="appRoute('about')">
|
||||
<span class="usermenu-item-icon">
|
||||
<%- include("../svg/info.svg") %>
|
||||
</span>
|
||||
<span class="usermenu-item-name">{{
|
||||
<span class="usermenu-item-name">{{
|
||||
$root.getLz("term.about")
|
||||
}}</span>
|
||||
</button>
|
||||
<button class="usermenu-item" @click="modals.settings = true">
|
||||
</button>
|
||||
<button class="usermenu-item" @click="modals.settings = true">
|
||||
<span class="usermenu-item-icon">
|
||||
<%- include("../svg/settings.svg") %>
|
||||
</span>
|
||||
<span class="usermenu-item-name">{{
|
||||
<span class="usermenu-item-name">{{
|
||||
$root.getLz("term.settings")
|
||||
}}</span>
|
||||
</button>
|
||||
<button class="usermenu-item" @click="unauthorize()">
|
||||
</button>
|
||||
<button class="usermenu-item" @click="unauthorize()">
|
||||
<span class="usermenu-item-icon" style="right: 2.5px">
|
||||
<%- include("../svg/log-out.svg") %>
|
||||
</span>
|
||||
<span class="usermenu-item-name">{{
|
||||
<span class="usermenu-item-name">{{
|
||||
$root.getLz("term.logout")
|
||||
}}</span>
|
||||
</button>
|
||||
<button class="usermenu-item" @click="quit()">
|
||||
</button>
|
||||
<button class="usermenu-item" @click="quit()">
|
||||
<span class="usermenu-item-icon" style="right: 2.5px">
|
||||
<%- include("../svg/x.svg") %>
|
||||
</span>
|
||||
<span class="usermenu-item-name">{{
|
||||
<span class="usermenu-item-name">{{
|
||||
$root.getLz("term.quit")
|
||||
}}</span>
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<transition name="sidebartransition">
|
||||
<cider-app-sidebar v-if="!chrome.sidebarCollapsed"></cider-app-sidebar>
|
||||
|
@ -141,9 +141,12 @@
|
|||
v-if="drawer.open && drawer.panel == 'lyrics' && lyrics && lyrics != [] && lyrics.length > 0">
|
||||
<div class="bgArtworkMaterial">
|
||||
<div class="bg-artwork-container">
|
||||
<img v-if="(cfg.visual.bg_artwork_rotation && animateBackground)" class="bg-artwork a" :src="$store.state.artwork.playerLCD">
|
||||
<img v-if="(cfg.visual.bg_artwork_rotation && animateBackground)" class="bg-artwork b" :src="$store.state.artwork.playerLCD">
|
||||
<img v-if="!(cfg.visual.bg_artwork_rotation && animateBackground)" class="bg-artwork no-animation" :src="$store.state.artwork.playerLCD">
|
||||
<img v-if="(cfg.visual.bg_artwork_rotation && animateBackground)" class="bg-artwork a"
|
||||
:src="$store.state.artwork.playerLCD">
|
||||
<img v-if="(cfg.visual.bg_artwork_rotation && animateBackground)" class="bg-artwork b"
|
||||
:src="$store.state.artwork.playerLCD">
|
||||
<img v-if="!(cfg.visual.bg_artwork_rotation && animateBackground)" class="bg-artwork no-animation"
|
||||
:src="$store.state.artwork.playerLCD">
|
||||
</div>
|
||||
</div>
|
||||
<lyrics-view v-if="drawer.panel == 'lyrics'" :time="mk.currentPlaybackTime - lyricOffset" :lyrics="lyrics"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<div class="app-chrome chrome-bottom" v-if="getThemeDirective('windowLayout') == 'twopanel'" :style="{'display': chrome.topChromeVisible ? '' : 'none'}">
|
||||
<div class="app-chrome chrome-bottom" v-if="getThemeDirective('windowLayout') == 'twopanel'"
|
||||
:style="{'display': chrome.topChromeVisible ? '' : 'none'}">
|
||||
<div class="app-chrome--left">
|
||||
<div class="app-chrome-item playback-controls">
|
||||
<template v-if="mkReady()">
|
||||
|
@ -16,15 +17,23 @@
|
|||
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
|
||||
</div>
|
||||
<div class="song-name">{{ mk.nowPlayingItem["attributes"]["name"] }}</div>
|
||||
<div class="song-artist" @click="getNowPlayingItemDetailed(`artist`)">{{ mk.nowPlayingItem["attributes"]["artistName"] }}</div>
|
||||
<div class="song-artist" @click="getNowPlayingItemDetailed(`artist`)">{{
|
||||
mk.nowPlayingItem["attributes"]["artistName"] }}
|
||||
</div>
|
||||
<div class="song-album" @click="getNowPlayingItemDetailed(`album`)">
|
||||
{{(mk.nowPlayingItem["attributes"]["albumName"]) ?
|
||||
(mk.nowPlayingItem["attributes"]["albumName"]) : "" }}
|
||||
</div>
|
||||
<hr>
|
||||
<div class="btn-group" style="width:100%;">
|
||||
<button class="md-btn md-btn-small" style="width: 100%;" @click="drawer.open = false; miniPlayer(true)">{{ $root.getLz("term.miniplayer") }}</button>
|
||||
<button class="md-btn md-btn-small" style="width: 100%;" @click="drawer.open = false; fullscreen(true)">{{ $root.getLz("term.fullscreenView") }}</button>
|
||||
<button class="md-btn md-btn-small" style="width: 100%;"
|
||||
@click="drawer.open = false; miniPlayer(true)">{{ $root.getLz("term.miniplayer")
|
||||
}}
|
||||
</button>
|
||||
<button class="md-btn md-btn-small" style="width: 100%;"
|
||||
@click="drawer.open = false; fullscreen(true)">{{
|
||||
$root.getLz("term.fullscreenView") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</b-popover>
|
||||
|
@ -39,20 +48,26 @@
|
|||
<div class="song-artist" @click="getNowPlayingItemDetailed(`artist`)">
|
||||
{{ mk.nowPlayingItem["attributes"]["artistName"] }}
|
||||
</div>
|
||||
<div class="song-album" @click="getNowPlayingItemDetailed('album')" v-if='mk.nowPlayingItem["attributes"]["albumName"]'>
|
||||
<div class="song-album" @click="getNowPlayingItemDetailed('album')"
|
||||
v-if='mk.nowPlayingItem["attributes"]["albumName"]'>
|
||||
{{(mk.nowPlayingItem["attributes"]["albumName"]) ?
|
||||
(mk.nowPlayingItem["attributes"]["albumName"]) : "" }}
|
||||
</div>
|
||||
<div class="chrome-icon-container">
|
||||
<div class="audio-type private-icon" v-if="cfg.general.privateEnabled === true"></div>
|
||||
<div class="audio-type spatial-icon" v-if="cfg.audio.maikiwiAudio.spatial === true"
|
||||
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization') + ' (' + getProfileLz('CTS', cfg.audio.maikiwiAudio.spatialProfile) + ')'" v-b-tooltip.hover
|
||||
<div class="audio-type spatial-icon" v-if="cfg.audio.maikiwiAudio.spatial === true"
|
||||
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization') + ' (' + getProfileLz('CTS', cfg.audio.maikiwiAudio.spatialProfile) + ')'"
|
||||
v-b-tooltip.hover
|
||||
></div>
|
||||
<div class="audio-type lossless-icon" v-if="(mk.nowPlayingItem?.localFilesMetadata?.lossless ?? false) === true"
|
||||
:title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container" v-b-tooltip.hover
|
||||
></div>
|
||||
<div class="audio-type ppe-icon" v-if="mk.nowPlayingItem.localFilesMetadata == null && cfg.audio.maikiwiAudio.ciderPPE === true"
|
||||
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')" v-b-tooltip.hover
|
||||
<div class="audio-type lossless-icon"
|
||||
v-if="(mk.nowPlayingItem?.localFilesMetadata?.lossless ?? false) === true"
|
||||
:title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container"
|
||||
v-b-tooltip.hover
|
||||
></div>
|
||||
<div class="audio-type ppe-icon"
|
||||
v-if="mk.nowPlayingItem.localFilesMetadata == null && cfg.audio.maikiwiAudio.ciderPPE === true"
|
||||
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')"
|
||||
v-b-tooltip.hover
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -75,7 +90,7 @@
|
|||
</div>
|
||||
<div class="playback-info">
|
||||
<div class="song-name">
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -86,8 +101,9 @@
|
|||
</div>
|
||||
<div class="app-chrome--center">
|
||||
<div class="app-chrome-playback-duration-bottom">
|
||||
<b-row v-if="mkReady()">
|
||||
<b-col sm="auto" v-if="!mk.nowPlayingItem?.isLiveRadioStation">{{ convertTime(getSongProgress()) }}</b-col>
|
||||
<b-row v-if="mkReady()">
|
||||
<b-col sm="auto" v-if="!mk.nowPlayingItem?.isLiveRadioStation">{{ convertTime(getSongProgress()) }}
|
||||
</b-col>
|
||||
<b-col sm="auto" v-else>--:--</b-col>
|
||||
<b-col>
|
||||
<input type="range" step="0.01" min="0" :style="progressBarStyle()"
|
||||
|
@ -96,13 +112,16 @@
|
|||
@touchend="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);"
|
||||
:max="mk.currentPlaybackDuration" :value="getSongProgress()">
|
||||
</b-col>
|
||||
<b-col sm="auto" v-if="!mk.nowPlayingItem?.isLiveRadioStation">{{ convertTime(mk.currentPlaybackDuration) }}</b-col>
|
||||
<b-col sm="auto" v-if="!mk.nowPlayingItem?.isLiveRadioStation">{{
|
||||
convertTime(mk.currentPlaybackDuration) }}
|
||||
</b-col>
|
||||
<b-col sm="auto" v-else>{{ getLz("term.live") }}</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
<div class="app-chrome-playback-controls">
|
||||
<div class="app-chrome-item">
|
||||
<button class="playback-button--small shuffle" v-if="mk.shuffleMode == 0" :class="isDisabled() && 'disabled'"
|
||||
<button class="playback-button--small shuffle" v-if="mk.shuffleMode == 0"
|
||||
:class="isDisabled() && 'disabled'"
|
||||
@click="mk.shuffleMode = 1" :title="$root.getLz('term.enableShuffle')"
|
||||
v-b-tooltip.hover></button>
|
||||
<button class="playback-button--small shuffle active" v-else :class="isDisabled() && 'disabled'"
|
||||
|
@ -114,7 +133,8 @@
|
|||
:title="$root.getLz('term.previous')" v-b-tooltip.hover></button>
|
||||
</div>
|
||||
<div class="app-chrome-item">
|
||||
<button class="playback-button stop" @click="mk.stop()" v-if="mk.isPlaying && mk.nowPlayingItem.attributes.playParams.kind == 'radioStation'"
|
||||
<button class="playback-button stop" @click="mk.stop()"
|
||||
v-if="mk.isPlaying && mk.nowPlayingItem.attributes.playParams.kind == 'radioStation'"
|
||||
:title="$root.getLz('term.stop')" v-b-tooltip.hover></button>
|
||||
<button class="playback-button pause" @click="mk.pause()" v-else-if="mk.isPlaying"
|
||||
:title="$root.getLz('term.pause')" v-b-tooltip.hover></button>
|
||||
|
@ -126,13 +146,16 @@
|
|||
:title="$root.getLz('term.next')" v-b-tooltip.hover></button>
|
||||
</div>
|
||||
<div class="app-chrome-item">
|
||||
<button class="playback-button--small repeat" v-if="mk.repeatMode == 0" :class="isDisabled() && 'disabled'"
|
||||
<button class="playback-button--small repeat" v-if="mk.repeatMode == 0"
|
||||
:class="isDisabled() && 'disabled'"
|
||||
@click="mk.repeatMode = 1" :title="$root.getLz('term.enableRepeatOne')"
|
||||
v-b-tooltip.hover></button>
|
||||
<button class="playback-button--small repeat repeatOne" @click="mk.repeatMode = 2" :class="isDisabled() && 'disabled'"
|
||||
<button class="playback-button--small repeat repeatOne" @click="mk.repeatMode = 2"
|
||||
:class="isDisabled() && 'disabled'"
|
||||
v-else-if="mk.repeatMode == 1" :title="$root.getLz('term.disableRepeatOne')"
|
||||
v-b-tooltip.hover></button>
|
||||
<button class="playback-button--small repeat active" @click="mk.repeatMode = 0" :class="isDisabled() && 'disabled'"
|
||||
<button class="playback-button--small repeat active" @click="mk.repeatMode = 0"
|
||||
:class="isDisabled() && 'disabled'"
|
||||
v-else-if="mk.repeatMode == 2" :title="$root.getLz('term.disableRepeat')"
|
||||
v-b-tooltip.hover></button>
|
||||
</div>
|
||||
|
@ -149,27 +172,27 @@
|
|||
v-b-tooltip.hover :title="formatVolumeTooltip()">
|
||||
</div>
|
||||
<div class="app-chrome-item generic">
|
||||
<button class="playback-button--small cast"
|
||||
<button class="playback-button--small cast"
|
||||
:title="$root.getLz('term.cast')"
|
||||
v-b-tooltip.hover
|
||||
@click="modals.castMenu = true"></button>
|
||||
</div>
|
||||
<div class="app-chrome-item generic">
|
||||
<button class="playback-button--small queue" :class="{'active': drawer.panel == 'queue'}"
|
||||
<button class="playback-button--small queue" :class="{'active': drawer.panel == 'queue'}"
|
||||
:title="$root.getLz('term.queue')"
|
||||
v-b-tooltip.hover
|
||||
@click="invokeDrawer('queue')"></button>
|
||||
</div>
|
||||
<div class="app-chrome-item generic">
|
||||
<template v-if="lyrics && lyrics != [] && lyrics.length > 0">
|
||||
<button class="playback-button--small lyrics"
|
||||
<button class="playback-button--small lyrics"
|
||||
:title="$root.getLz('term.lyrics')"
|
||||
v-b-tooltip.hover
|
||||
:class="{'active': drawer.panel == 'lyrics'}"
|
||||
@click="invokeDrawer('lyrics')"></button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button class="playback-button--small lyrics"
|
||||
<button class="playback-button--small lyrics"
|
||||
:style="{'opacity': 0.3, 'pointer-events': 'none'}"></button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,286 +1,313 @@
|
|||
<div class="app-chrome" :style="{'display': chrome.topChromeVisible ? '' : 'none'}">
|
||||
<div class="app-chrome--left">
|
||||
<div class="app-chrome-item full-height" v-if="chrome.windowControlPosition == 'left' && !chrome.nativeControls">
|
||||
<div class="window-controls-macos">
|
||||
<div class="close" @click="ipcRenderer.send('close')"></div>
|
||||
<div class="minimize" @click="ipcRenderer.send('minimize')"></div>
|
||||
<div class="minmax restore" v-if="chrome.maximized" @click="ipcRenderer.send('maximize')"></div>
|
||||
<div class="minmax" v-else @click="ipcRenderer.send('maximize')"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-chrome-item full-height" v-else>
|
||||
<button class="app-mainmenu" @blur="mainMenuVisibility(false)" @click="mainMenuVisibility(true)"
|
||||
@contextmenu="mainMenuVisibility(true)" :class="{active: chrome.menuOpened}"
|
||||
:aria-label="$root.getLz('term.quickNav')"></button>
|
||||
</div>
|
||||
<template v-if="getThemeDirective('appNavigation') != 'seperate'">
|
||||
<div class="vdiv" v-if="getThemeDirective('windowLayout') == 'twopanel'"></div>
|
||||
<div class="app-chrome-item">
|
||||
<button class="playback-button navigation" @click="navigateBack()" :title="$root.getLz('term.navigateBack')"
|
||||
v-b-tooltip.hover>
|
||||
<svg-icon url="./views/svg/chevron-left.svg"></svg-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="app-chrome-item">
|
||||
<button class="playback-button navigation" @click="navigateForward()"
|
||||
:title="$root.getLz('term.navigateForward')" v-b-tooltip.hover>
|
||||
<svg-icon url="./views/svg/chevron-right.svg"></svg-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="app-chrome-item" v-if="getThemeDirective('windowLayout') == 'twopanel'">
|
||||
<button class="playback-button collapseLibrary" v-b-tooltip.hover
|
||||
:title="chrome.sidebarCollapsed ? getLz('action.showLibrary') : getLz('action.hideLibrary')"
|
||||
@click="chrome.sidebarCollapsed = !chrome.sidebarCollapsed">
|
||||
<transition name="fade">
|
||||
<span v-if="chrome.sidebarCollapsed"></span>
|
||||
</transition>
|
||||
<transition name="fade">
|
||||
<span v-if="!chrome.sidebarCollapsed"></span>
|
||||
</transition>
|
||||
<div class="app-chrome--left">
|
||||
<div class="app-chrome-item full-height"
|
||||
v-if="chrome.windowControlPosition == 'left' && !chrome.nativeControls">
|
||||
<div class="window-controls-macos">
|
||||
<div class="close" @click="ipcRenderer.send('close')"></div>
|
||||
<div class="minimize" @click="ipcRenderer.send('minimize')"></div>
|
||||
<div class="minmax restore" v-if="chrome.maximized" @click="ipcRenderer.send('maximize')"></div>
|
||||
<div class="minmax" v-else @click="ipcRenderer.send('maximize')"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-chrome-item full-height" v-else>
|
||||
<button class="app-mainmenu" @blur="mainMenuVisibility(false)" @click="mainMenuVisibility(true)"
|
||||
@contextmenu="mainMenuVisibility(true)" :class="{active: chrome.menuOpened}"
|
||||
:aria-label="$root.getLz('term.quickNav')"></button>
|
||||
</div>
|
||||
<template v-if="getThemeDirective('appNavigation') != 'seperate'">
|
||||
<div class="vdiv" v-if="getThemeDirective('windowLayout') == 'twopanel'"></div>
|
||||
<div class="app-chrome-item">
|
||||
<button class="playback-button navigation" @click="navigateBack()"
|
||||
:title="$root.getLz('term.navigateBack')"
|
||||
v-b-tooltip.hover>
|
||||
<svg-icon url="./views/svg/chevron-left.svg"></svg-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="app-chrome-item">
|
||||
<button class="playback-button navigation" @click="navigateForward()"
|
||||
:title="$root.getLz('term.navigateForward')" v-b-tooltip.hover>
|
||||
<svg-icon url="./views/svg/chevron-right.svg"></svg-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="app-chrome-item" v-if="getThemeDirective('windowLayout') == 'twopanel'">
|
||||
<button class="playback-button collapseLibrary" v-b-tooltip.hover
|
||||
:title="chrome.sidebarCollapsed ? getLz('action.showLibrary') : getLz('action.hideLibrary')"
|
||||
@click="chrome.sidebarCollapsed = !chrome.sidebarCollapsed">
|
||||
<transition name="fade">
|
||||
<span v-if="chrome.sidebarCollapsed"></span>
|
||||
</transition>
|
||||
<transition name="fade">
|
||||
<span v-if="!chrome.sidebarCollapsed"></span>
|
||||
</transition>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="vdiv display--large" v-if="getThemeDirective('windowLayout') != 'twopanel'"></div>
|
||||
</template>
|
||||
<template v-if="getThemeDirective('windowLayout') != 'twopanel'">
|
||||
<div class="app-chrome-item playback-control-buttons">
|
||||
<div class="app-chrome-item display--large">
|
||||
<button class="playback-button--small shuffle" v-if="mk.shuffleMode == 0" :class="isDisabled() && 'disabled'"
|
||||
@click="mk.shuffleMode = 1" :title="$root.getLz('term.enableShuffle')" v-b-tooltip.hover></button>
|
||||
<button class="playback-button--small shuffle active" v-else :class="isDisabled() && 'disabled'"
|
||||
@click="mk.shuffleMode = 0" :title="$root.getLz('term.disableShuffle')" v-b-tooltip.hover></button>
|
||||
</div>
|
||||
<div class="app-chrome-item display--large">
|
||||
<button class="playback-button previous" @click="prevButton()" :class="isPrevDisabled() && 'disabled'"
|
||||
:title="$root.getLz('term.previous')" v-b-tooltip.hover></button>
|
||||
</div>
|
||||
<div class="app-chrome-item display--large">
|
||||
<button class="playback-button stop" @click="mk.stop()"
|
||||
v-if="mk.isPlaying && mk.nowPlayingItem.attributes.playParams.kind == 'radioStation'"
|
||||
:title="$root.getLz('term.stop')" v-b-tooltip.hover></button>
|
||||
<button class="playback-button pause" @click="mk.pause()" v-else-if="mk.isPlaying"
|
||||
:title="$root.getLz('term.pause')" v-b-tooltip.hover></button>
|
||||
<button class="playback-button play" @click="mk.play()" v-else :title="$root.getLz('term.play')"
|
||||
v-b-tooltip.hover></button>
|
||||
</div>
|
||||
<div class="app-chrome-item display--large">
|
||||
<button class="playback-button next" @click="skipToNextItem()" :class="isNextDisabled() && 'disabled'"
|
||||
:title="$root.getLz('term.next')" v-b-tooltip.hover></button>
|
||||
</div>
|
||||
<div class="app-chrome-item display--large">
|
||||
<button class="playback-button--small repeat" v-if="mk.repeatMode == 0" :class="isDisabled() && 'disabled'"
|
||||
@click="mk.repeatMode = 1" :title="$root.getLz('term.enableRepeatOne')" v-b-tooltip.hover></button>
|
||||
<button class="playback-button--small repeat repeatOne" @click="mk.repeatMode = 2"
|
||||
:class="isDisabled() && 'disabled'" v-else-if="mk.repeatMode == 1"
|
||||
:title="$root.getLz('term.disableRepeatOne')" v-b-tooltip.hover></button>
|
||||
<button class="playback-button--small repeat active" @click="mk.repeatMode = 0"
|
||||
:class="isDisabled() && 'disabled'" v-else-if="mk.repeatMode == 2" :title="$root.getLz('term.disableRepeat')"
|
||||
v-b-tooltip.hover></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="app-chrome--center">
|
||||
<div class="app-chrome-item playback-controls" v-if="getThemeDirective('windowLayout') != 'twopanel'">
|
||||
<template v-if="mkReady()">
|
||||
<div class="app-playback-controls" @mouseover="chrome.progresshover = true"
|
||||
@mouseleave="chrome.progresshover = false" @contextmenu="nowPlayingContextMenu">
|
||||
<div class="artwork" id="artworkLCD">
|
||||
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
|
||||
</div>
|
||||
<b-popover custom-class="mediainfo-popover" target="artworkLCD" triggers="hover" placement="bottom">
|
||||
<div class="content">
|
||||
<div class="shadow-artwork">
|
||||
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
|
||||
</div>
|
||||
<div class="popover-artwork">
|
||||
<mediaitem-artwork :size="210" :url="currentArtUrl"></mediaitem-artwork>
|
||||
</div>
|
||||
<div class="song-name">
|
||||
{{ mk.nowPlayingItem["attributes"]["name"] }}
|
||||
</div>
|
||||
<div class="song-artist" @click="getNowPlayingItemDetailed(`artist`)">
|
||||
{{ mk.nowPlayingItem["attributes"]["artistName"] }}
|
||||
</div>
|
||||
<div class="song-album" @click="getNowPlayingItemDetailed(`album`)">
|
||||
{{
|
||||
mk.nowPlayingItem["attributes"]["albumName"]
|
||||
? mk.nowPlayingItem["attributes"]["albumName"]
|
||||
: ""
|
||||
}}
|
||||
</div>
|
||||
<hr />
|
||||
<div class="btn-group" style="width: 100%">
|
||||
<button class="md-btn md-btn-small" style="width: 100%" @click="drawer.open = false; miniPlayer(true)">
|
||||
{{ $root.getLz("term.miniplayer") }}
|
||||
</button>
|
||||
<button class="md-btn md-btn-small" style="width: 100%" @click="drawer.open = false; fullscreen(true)">
|
||||
{{ $root.getLz("term.fullscreenView") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</b-popover>
|
||||
<div class="playback-info">
|
||||
<div class="chrome-icon-container">
|
||||
<div class="audio-type private-icon" v-if="cfg.general.privateEnabled === true"></div>
|
||||
<div class="audio-type spatial-icon" v-if="cfg.audio.maikiwiAudio.spatial === true"
|
||||
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization') + ' (' + getProfileLz('CTS', cfg.audio.maikiwiAudio.spatialProfile) + ')'" v-b-tooltip.hover
|
||||
></div>
|
||||
<div class="audio-type lossless-icon" v-if="(mk.nowPlayingItem?.localFilesMetadata?.lossless ?? false) === true"
|
||||
:title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container" v-b-tooltip.hover
|
||||
></div>
|
||||
<div class="audio-type ppe-icon" v-if="mk.nowPlayingItem.localFilesMetadata == null && cfg.audio.maikiwiAudio.ciderPPE === true"
|
||||
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')" v-b-tooltip.hover
|
||||
></div>
|
||||
<div class="vdiv display--large" v-if="getThemeDirective('windowLayout') != 'twopanel'"></div>
|
||||
</template>
|
||||
<template v-if="getThemeDirective('windowLayout') != 'twopanel'">
|
||||
<div class="app-chrome-item playback-control-buttons">
|
||||
<div class="app-chrome-item display--large">
|
||||
<button class="playback-button--small shuffle" v-if="mk.shuffleMode == 0"
|
||||
:class="isDisabled() && 'disabled'"
|
||||
@click="mk.shuffleMode = 1" :title="$root.getLz('term.enableShuffle')"
|
||||
v-b-tooltip.hover></button>
|
||||
<button class="playback-button--small shuffle active" v-else :class="isDisabled() && 'disabled'"
|
||||
@click="mk.shuffleMode = 0" :title="$root.getLz('term.disableShuffle')"
|
||||
v-b-tooltip.hover></button>
|
||||
</div>
|
||||
<div class="app-chrome-item display--large">
|
||||
<button class="playback-button previous" @click="prevButton()"
|
||||
:class="isPrevDisabled() && 'disabled'"
|
||||
:title="$root.getLz('term.previous')" v-b-tooltip.hover></button>
|
||||
</div>
|
||||
<div class="app-chrome-item display--large">
|
||||
<button class="playback-button stop" @click="mk.stop()"
|
||||
v-if="mk.isPlaying && mk.nowPlayingItem.attributes.playParams.kind == 'radioStation'"
|
||||
:title="$root.getLz('term.stop')" v-b-tooltip.hover></button>
|
||||
<button class="playback-button pause" @click="mk.pause()" v-else-if="mk.isPlaying"
|
||||
:title="$root.getLz('term.pause')" v-b-tooltip.hover></button>
|
||||
<button class="playback-button play" @click="mk.play()" v-else :title="$root.getLz('term.play')"
|
||||
v-b-tooltip.hover></button>
|
||||
</div>
|
||||
<div class="app-chrome-item display--large">
|
||||
<button class="playback-button next" @click="skipToNextItem()"
|
||||
:class="isNextDisabled() && 'disabled'"
|
||||
:title="$root.getLz('term.next')" v-b-tooltip.hover></button>
|
||||
</div>
|
||||
<div class="app-chrome-item display--large">
|
||||
<button class="playback-button--small repeat" v-if="mk.repeatMode == 0"
|
||||
:class="isDisabled() && 'disabled'"
|
||||
@click="mk.repeatMode = 1" :title="$root.getLz('term.enableRepeatOne')"
|
||||
v-b-tooltip.hover></button>
|
||||
<button class="playback-button--small repeat repeatOne" @click="mk.repeatMode = 2"
|
||||
:class="isDisabled() && 'disabled'" v-else-if="mk.repeatMode == 1"
|
||||
:title="$root.getLz('term.disableRepeatOne')" v-b-tooltip.hover></button>
|
||||
<button class="playback-button--small repeat active" @click="mk.repeatMode = 0"
|
||||
:class="isDisabled() && 'disabled'" v-else-if="mk.repeatMode == 2"
|
||||
:title="$root.getLz('term.disableRepeat')"
|
||||
v-b-tooltip.hover></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-rect">
|
||||
<div class="song-name"
|
||||
:class="[isElementOverflowing('#app-main > div.app-chrome > div.app-chrome--center > div > div > div.playback-info > div.song-name') ? 'marquee' : '']">
|
||||
{{ mk.nowPlayingItem["attributes"]["name"] }}
|
||||
<div class="explicit-icon" v-if="mk.nowPlayingItem['attributes']['contentRating'] == 'explicit'"
|
||||
style="display: inline-block"></div>
|
||||
</div>
|
||||
<div class="song-artist-album">
|
||||
<div class="song-artist-album-content"
|
||||
:class="[isElementOverflowing('#app-main > .app-chrome .app-chrome-item > .app-playback-controls > div >.song-artist-album > .song-artist-album-content') ? 'marquee' : '']"
|
||||
style="
|
||||
</template>
|
||||
</div>
|
||||
<div class="app-chrome--center">
|
||||
<div class="app-chrome-item playback-controls" v-if="getThemeDirective('windowLayout') != 'twopanel'">
|
||||
<template v-if="mkReady()">
|
||||
<div class="app-playback-controls" @mouseover="chrome.progresshover = true"
|
||||
@mouseleave="chrome.progresshover = false" @contextmenu="nowPlayingContextMenu">
|
||||
<div class="artwork" id="artworkLCD">
|
||||
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
|
||||
</div>
|
||||
<b-popover custom-class="mediainfo-popover" target="artworkLCD" triggers="hover" placement="bottom">
|
||||
<div class="content">
|
||||
<div class="shadow-artwork">
|
||||
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
|
||||
</div>
|
||||
<div class="popover-artwork">
|
||||
<mediaitem-artwork :size="210" :url="currentArtUrl"></mediaitem-artwork>
|
||||
</div>
|
||||
<div class="song-name">
|
||||
{{ mk.nowPlayingItem["attributes"]["name"] }}
|
||||
</div>
|
||||
<div class="song-artist" @click="getNowPlayingItemDetailed(`artist`)">
|
||||
{{ mk.nowPlayingItem["attributes"]["artistName"] }}
|
||||
</div>
|
||||
<div class="song-album" @click="getNowPlayingItemDetailed(`album`)">
|
||||
{{
|
||||
mk.nowPlayingItem["attributes"]["albumName"]
|
||||
? mk.nowPlayingItem["attributes"]["albumName"]
|
||||
: ""
|
||||
}}
|
||||
</div>
|
||||
<hr />
|
||||
<div class="btn-group" style="width: 100%">
|
||||
<button class="md-btn md-btn-small" style="width: 100%"
|
||||
@click="drawer.open = false; miniPlayer(true)">
|
||||
{{ $root.getLz("term.miniplayer") }}
|
||||
</button>
|
||||
<button class="md-btn md-btn-small" style="width: 100%"
|
||||
@click="drawer.open = false; fullscreen(true)">
|
||||
{{ $root.getLz("term.fullscreenView") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</b-popover>
|
||||
<div class="playback-info">
|
||||
<div class="chrome-icon-container">
|
||||
<div class="audio-type private-icon" v-if="cfg.general.privateEnabled === true"></div>
|
||||
<div class="audio-type spatial-icon" v-if="cfg.audio.maikiwiAudio.spatial === true"
|
||||
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization') + ' (' + getProfileLz('CTS', cfg.audio.maikiwiAudio.spatialProfile) + ')'"
|
||||
v-b-tooltip.hover
|
||||
></div>
|
||||
<div class="audio-type lossless-icon"
|
||||
v-if="(mk.nowPlayingItem?.localFilesMetadata?.lossless ?? false) === true"
|
||||
:title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container"
|
||||
v-b-tooltip.hover
|
||||
></div>
|
||||
<div class="audio-type ppe-icon"
|
||||
v-if="mk.nowPlayingItem.localFilesMetadata == null && cfg.audio.maikiwiAudio.ciderPPE === true"
|
||||
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')"
|
||||
v-b-tooltip.hover
|
||||
></div>
|
||||
</div>
|
||||
<div class="info-rect">
|
||||
<div class="song-name"
|
||||
:class="[isElementOverflowing('#app-main > div.app-chrome > div.app-chrome--center > div > div > div.playback-info > div.song-name') ? 'marquee' : '']">
|
||||
{{ mk.nowPlayingItem["attributes"]["name"] }}
|
||||
<div class="explicit-icon"
|
||||
v-if="mk.nowPlayingItem['attributes']['contentRating'] == 'explicit'"
|
||||
style="display: inline-block"></div>
|
||||
</div>
|
||||
<div class="song-artist-album">
|
||||
<div class="song-artist-album-content"
|
||||
:class="[isElementOverflowing('#app-main > .app-chrome .app-chrome-item > .app-playback-controls > div >.song-artist-album > .song-artist-album-content') ? 'marquee' : '']"
|
||||
style="
|
||||
display: inline-block;
|
||||
-webkit-box-orient: horizontal;
|
||||
white-space: nowrap;
|
||||
">
|
||||
<div class="item-navigate song-artist" style="display: inline-block"
|
||||
@click="getNowPlayingItemDetailed(`artist`)">
|
||||
{{ mk.nowPlayingItem["attributes"]["artistName"] }}
|
||||
</div>
|
||||
<div class="song-artist item-navigate" style="display: inline-block"
|
||||
@click="getNowPlayingItemDetailed('album')"
|
||||
v-if="mk.nowPlayingItem['attributes']['albumName'] != ''">
|
||||
<div class="separator" style="display: inline-block">
|
||||
{{ "—" }}
|
||||
<div class="item-navigate song-artist" style="display: inline-block"
|
||||
@click="getNowPlayingItemDetailed(`artist`)">
|
||||
{{ mk.nowPlayingItem["attributes"]["artistName"] }}
|
||||
</div>
|
||||
<div class="song-artist item-navigate" style="display: inline-block"
|
||||
@click="getNowPlayingItemDetailed('album')"
|
||||
v-if="mk.nowPlayingItem['attributes']['albumName'] != ''">
|
||||
<div class="separator" style="display: inline-block">
|
||||
{{ "—" }}
|
||||
</div>
|
||||
{{
|
||||
mk.nowPlayingItem["attributes"]["albumName"]
|
||||
? mk.nowPlayingItem["attributes"]["albumName"]
|
||||
: ""
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="song-progress">
|
||||
<div class="song-duration" style="justify-content: space-between; height: 1px"
|
||||
:style="[chrome.progresshover ? {'display': 'flex'} : {'display' : 'none'} ]">
|
||||
<p style="width: auto">{{ convertTime(getSongProgress()) }}</p>
|
||||
<p style="width: auto">
|
||||
{{ convertTime(mk.currentPlaybackDuration) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<input type="range" step="0.01" min="0" :style="progressBarStyle()"
|
||||
@input="playerLCD.desiredDuration = $event.target.value;playerLCD.userInteraction = true"
|
||||
@mouseup="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);"
|
||||
@touchend="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);"
|
||||
:max="mk.currentPlaybackDuration" :value="getSongProgress()" />
|
||||
</div>
|
||||
</div>
|
||||
{{
|
||||
mk.nowPlayingItem["attributes"]["albumName"]
|
||||
? mk.nowPlayingItem["attributes"]["albumName"]
|
||||
: ""
|
||||
}}
|
||||
</div>
|
||||
<template v-if="mk.nowPlayingItem['attributes']['playParams']">
|
||||
<div class="actions">
|
||||
<button class="lcdMenu" @click="nowPlayingContextMenu" :title="$root.getLz('term.more')"
|
||||
v-b-tooltip.hover>
|
||||
<div class="svg-icon"></div>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="song-progress">
|
||||
<div class="song-duration" style="justify-content: space-between; height: 1px"
|
||||
:style="[chrome.progresshover ? {'display': 'flex'} : {'display' : 'none'} ]">
|
||||
<p style="width: auto">{{ convertTime(getSongProgress()) }}</p>
|
||||
<p style="width: auto">
|
||||
{{ convertTime(mk.currentPlaybackDuration) }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="app-playback-controls">
|
||||
<div class="artwork" id="artworkLCD" style="pointer-events: none;">
|
||||
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
|
||||
</div>
|
||||
<div class="playback-info">
|
||||
<div class="info-rect">
|
||||
|
||||
<input type="range" step="0.01" min="0" :style="progressBarStyle()"
|
||||
@input="playerLCD.desiredDuration = $event.target.value;playerLCD.userInteraction = true"
|
||||
@mouseup="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);"
|
||||
@touchend="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);"
|
||||
:max="mk.currentPlaybackDuration" :value="getSongProgress()" />
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="mk.nowPlayingItem['attributes']['playParams']">
|
||||
<div class="actions">
|
||||
<button class="lcdMenu" @click="nowPlayingContextMenu" :title="$root.getLz('term.more')"
|
||||
v-b-tooltip.hover>
|
||||
<div class="svg-icon"></div>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="app-playback-controls">
|
||||
<div class="artwork" id="artworkLCD" style="pointer-events: none;">
|
||||
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
|
||||
</div>
|
||||
<div class="playback-info">
|
||||
<div class="info-rect">
|
||||
|
||||
<div class="app-chrome-item" v-else>
|
||||
<div class="top-nav-group">
|
||||
<sidebar-library-item :name="$root.getLz('home.title')" svg-icon="./assets/feather/home.svg"
|
||||
svg-icon-name="home" page="home">
|
||||
</sidebar-library-item>
|
||||
<sidebar-library-item :name="$root.getLz('term.listenNow')" svg-icon="./assets/feather/play-circle.svg"
|
||||
svg-icon-name="listenNow"
|
||||
page="listen_now"></sidebar-library-item>
|
||||
<sidebar-library-item :name="$root.getLz('term.browse')" svg-icon="./assets/feather/globe.svg"
|
||||
svg-icon-name="browse" page="browse">
|
||||
</sidebar-library-item>
|
||||
<sidebar-library-item :name="$root.getLz('term.radio')" svg-icon="./assets/feather/radio.svg"
|
||||
svg-icon-name="radio" page="radio">
|
||||
</sidebar-library-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="app-chrome-item" v-else>
|
||||
<div class="top-nav-group">
|
||||
<sidebar-library-item :name="$root.getLz('home.title')" svg-icon="./assets/feather/home.svg" svg-icon-name="home" page="home">
|
||||
</sidebar-library-item>
|
||||
<sidebar-library-item :name="$root.getLz('term.listenNow')" svg-icon="./assets/feather/play-circle.svg" svg-icon-name="listenNow"
|
||||
page="listen_now"></sidebar-library-item>
|
||||
<sidebar-library-item :name="$root.getLz('term.browse')" svg-icon="./assets/feather/globe.svg" svg-icon-name="browse" page="browse">
|
||||
</sidebar-library-item>
|
||||
<sidebar-library-item :name="$root.getLz('term.radio')" svg-icon="./assets/feather/radio.svg" svg-icon-name="radio" page="radio">
|
||||
</sidebar-library-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-chrome--right">
|
||||
<template v-if="getThemeDirective('windowLayout') != 'twopanel'">
|
||||
<div class="app-chrome-item volume display--large">
|
||||
<button class="volume-button--small volume" @click="muteButtonPressed()"
|
||||
:class="{'active': this.cfg.audio.volume == 0}"
|
||||
:title="cfg.audio.muted ? $root.getLz('term.unmute') : $root.getLz('term.mute')" v-b-tooltip.hover></button>
|
||||
<input type="range" @wheel="volumeWheel" :step="cfg.audio.volumeStep" min="0" :max="cfg.audio.maxVolume"
|
||||
v-model="mk.volume" v-if="typeof mk.volume != 'undefined'" @change="checkMuteChange()" v-b-tooltip.hover
|
||||
:title="formatVolumeTooltip()" />
|
||||
</div>
|
||||
<div class="app-chrome-item generic">
|
||||
<button class="playback-button--small cast" :title="$root.getLz('term.cast')"
|
||||
@click="modals.castMenu = true"
|
||||
v-b-tooltip.hover></button>
|
||||
</div>
|
||||
<div class="app-chrome-item generic">
|
||||
<button class="playback-button--small queue" :title="$root.getLz('term.queue')" v-b-tooltip.hover
|
||||
:class="{'active': drawer.panel == 'queue'}" @click="invokeDrawer('queue')"></button>
|
||||
</div>
|
||||
<div class="app-chrome-item generic">
|
||||
<template v-if="lyrics && lyrics != [] && lyrics.length > 0">
|
||||
<button class="playback-button--small lyrics" :title="$root.getLz('term.lyrics')" v-b-tooltip.hover
|
||||
:class="{'active': drawer.panel == 'lyrics'}" @click="invokeDrawer('lyrics')"></button>
|
||||
<div class="app-chrome--right">
|
||||
<template v-if="getThemeDirective('windowLayout') != 'twopanel'">
|
||||
<div class="app-chrome-item volume display--large">
|
||||
<button class="volume-button--small volume" @click="muteButtonPressed()"
|
||||
:class="{'active': this.cfg.audio.volume == 0}"
|
||||
:title="cfg.audio.muted ? $root.getLz('term.unmute') : $root.getLz('term.mute')"
|
||||
v-b-tooltip.hover></button>
|
||||
<input type="range" @wheel="volumeWheel" :step="cfg.audio.volumeStep" min="0" :max="cfg.audio.maxVolume"
|
||||
v-model="mk.volume" v-if="typeof mk.volume != 'undefined'" @change="checkMuteChange()"
|
||||
v-b-tooltip.hover
|
||||
:title="formatVolumeTooltip()" />
|
||||
</div>
|
||||
<div class="app-chrome-item generic">
|
||||
<button class="playback-button--small cast" :title="$root.getLz('term.cast')"
|
||||
@click="modals.castMenu = true"
|
||||
v-b-tooltip.hover></button>
|
||||
</div>
|
||||
<div class="app-chrome-item generic">
|
||||
<button class="playback-button--small queue" :title="$root.getLz('term.queue')" v-b-tooltip.hover
|
||||
:class="{'active': drawer.panel == 'queue'}" @click="invokeDrawer('queue')"></button>
|
||||
</div>
|
||||
<div class="app-chrome-item generic">
|
||||
<template v-if="lyrics && lyrics != [] && lyrics.length > 0">
|
||||
<button class="playback-button--small lyrics" :title="$root.getLz('term.lyrics')" v-b-tooltip.hover
|
||||
:class="{'active': drawer.panel == 'lyrics'}" @click="invokeDrawer('lyrics')"></button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button class="playback-button--small lyrics"
|
||||
:style="{'opacity': 0.3, 'pointer-events': 'none'}"></button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button class="playback-button--small lyrics" :style="{'opacity': 0.3, 'pointer-events': 'none'}" ></button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="app-chrome-item search">
|
||||
<div class="search-input-container">
|
||||
<div class="search-input--icon"></div>
|
||||
<input type="search" spellcheck="false" @click="$root.appRoute('search');" @focus="search.showHints = true"
|
||||
@blur="setTimeout(()=>{search.showHints = false}, 300)"
|
||||
v-on:keyup.enter="searchQuery();search.showHints = false" @change="$root.appRoute('search');" @input="getSearchHints()"
|
||||
:placeholder="$root.getLz('term.search') + '...'" v-model="search.term" ref="searchInput"
|
||||
class="search-input" />
|
||||
<div class="app-chrome-item search">
|
||||
<div class="search-input-container">
|
||||
<div class="search-input--icon"></div>
|
||||
<input type="search" spellcheck="false" @click="$root.appRoute('search');"
|
||||
@focus="search.showHints = true"
|
||||
@blur="setTimeout(()=>{search.showHints = false}, 300)"
|
||||
v-on:keyup.enter="searchQuery();search.showHints = false" @change="$root.appRoute('search');"
|
||||
@input="getSearchHints()"
|
||||
:placeholder="$root.getLz('term.search') + '...'" v-model="search.term" ref="searchInput"
|
||||
class="search-input" />
|
||||
|
||||
<div class="search-hints-container" v-if="search.showHints && search.hints.length != 0">
|
||||
<div class="search-hints">
|
||||
<button class="search-hint text-overflow-elipsis" v-for="hint in search.hints"
|
||||
@click="search.term = hint;search.showHints = false;searchQuery(hint)">
|
||||
{{ hint }}
|
||||
</button>
|
||||
<div class="search-hints-container" v-if="search.showHints && search.hints.length != 0">
|
||||
<div class="search-hints">
|
||||
<button class="search-hint text-overflow-elipsis" v-for="hint in search.hints"
|
||||
@click="search.term = hint;search.showHints = false;searchQuery(hint)">
|
||||
{{ hint }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="app-chrome-item full-height" id="window-controls-container"
|
||||
v-if="chrome.windowControlPosition == 'right' && !chrome.nativeControls">
|
||||
<div class="window-controls">
|
||||
<div class="minimize" @click="ipcRenderer.send('minimize')"></div>
|
||||
<div class="minmax restore" v-if="chrome.maximized" @click="ipcRenderer.send('maximize')"></div>
|
||||
<div class="minmax" v-else @click="ipcRenderer.send('maximize')"></div>
|
||||
<div class="close" @click="ipcRenderer.send('close')"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="app-chrome-item full-height" id="window-controls-container"
|
||||
v-if="chrome.windowControlPosition == 'right' && !chrome.nativeControls">
|
||||
<div class="window-controls">
|
||||
<div class="minimize" @click="ipcRenderer.send('minimize')"></div>
|
||||
<div class="minmax restore" v-if="chrome.maximized" @click="ipcRenderer.send('maximize')"></div>
|
||||
<div class="minmax" v-else @click="ipcRenderer.send('maximize')"></div>
|
||||
<div class="close" @click="ipcRenderer.send('close')"></div>
|
||||
</div>
|
||||
<div class="app-chrome-item full-height" v-else-if="platform != 'darwin' && !chrome.nativeControls">
|
||||
<button class="app-mainmenu" @blur="mainMenuVisibility(false)" @click="mainMenuVisibility(true)"
|
||||
@contextmenu="mainMenuVisibility(true)" :class="{active: chrome.menuOpened}"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-chrome-item full-height" v-else-if="platform != 'darwin' && !chrome.nativeControls">
|
||||
<button class="app-mainmenu" @blur="mainMenuVisibility(false)" @click="mainMenuVisibility(true)"
|
||||
@contextmenu="mainMenuVisibility(true)" :class="{active: chrome.menuOpened}"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -50,10 +50,10 @@
|
|||
<div id="apple-music-video-player-controls">
|
||||
<div id="player-exit" title="Close" @click="exitMV();fullscreen(false);">
|
||||
<svg fill="white" xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21"
|
||||
aria-role="presentation" focusable="false" @click="exitMV();fullscreen(false);">
|
||||
aria-role="presentation" focusable="false" @click="exitMV();fullscreen(false);">
|
||||
<path
|
||||
d="M10.5 21C4.724 21 0 16.275 0 10.5S4.724 0 10.5 0 21 4.725 21 10.5 16.276 21 10.5 21zm-3.543-5.967a.96.96 0 00.693-.295l2.837-2.842 2.85 2.842c.167.167.41.295.693.295.552 0 1.001-.461 1.001-1.012 0-.281-.115-.512-.295-.704L11.899 10.5l2.85-2.855a.875.875 0 00.295-.68c0-.55-.45-.998-1.001-.998a.871.871 0 00-.668.295l-2.888 2.855-2.862-2.843a.891.891 0 00-.668-.281.99.99 0 00-1.001.986c0 .269.116.512.295.678L9.088 10.5l-2.837 2.843a.926.926 0 00-.295.678c0 .551.45 1.012 1.001 1.012z"
|
||||
fill-rule="nonzero"/>
|
||||
fill-rule="nonzero" />
|
||||
</svg>
|
||||
</div>
|
||||
<div id="captions" v-if="lyricon">{{((lyricon) ? ((lyrics.length > 0 && lyrics[currentLyricsLine] &&
|
||||
|
@ -67,7 +67,7 @@
|
|||
</div>
|
||||
<div class="playback-info music-player-info">
|
||||
<div class="song-artist-album-content"
|
||||
style="display: inline-block; -webkit-box-orient: horizontal; white-space: nowrap;">
|
||||
style="display: inline-block; -webkit-box-orient: horizontal; white-space: nowrap;">
|
||||
<div class="song-artist" style="display: inline-block">
|
||||
{{ mk.nowPlayingItem?.attributes?.artistName ?? '' }}
|
||||
</div>
|
||||
|
@ -85,7 +85,7 @@
|
|||
@mouseup="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);"
|
||||
@touchend="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);"
|
||||
:max="mk.currentPlaybackDuration" :value="getSongProgress()">
|
||||
<p style="width: auto">{{ convertTime(mk.currentPlaybackDuration) }}
|
||||
<p style="width: auto">{{ convertTime(mk.currentPlaybackDuration) }}
|
||||
</div>
|
||||
<div class="app-chrome-item display--large">
|
||||
<div class="app-chrome-item volume display--large">
|
||||
|
@ -93,7 +93,8 @@
|
|||
:class="{'active': this.cfg.audio.volume == 0}"
|
||||
:title="cfg.audio.muted ? $root.getLz('term.unmute') : $root.getLz('term.mute')"
|
||||
v-b-tooltip.hover></button>
|
||||
<input type="range" @wheel="volumeWheel" :step="cfg.audio.volumeStep" min="0" :max="cfg.audio.maxVolume"
|
||||
<input type="range" @wheel="volumeWheel" :step="cfg.audio.volumeStep" min="0"
|
||||
:max="cfg.audio.maxVolume"
|
||||
v-model="mk.volume" v-if="typeof mk.volume != 'undefined'" @change="checkMuteChange()"
|
||||
v-b-tooltip.hover :title="formatVolumeTooltip()">
|
||||
</div>
|
||||
|
@ -102,30 +103,30 @@
|
|||
<button class="playback-button play" @click="mk.play()" v-else
|
||||
:title="$root.getLz('term.play')" v-b-tooltip.hover></button>
|
||||
<div class="app-chrome-item generic">
|
||||
<template v-if="lyrics && lyrics != [] && lyrics.length > 0">
|
||||
<button class="playback-button--small lyrics"
|
||||
:title="$root.getLz('term.lyrics')"
|
||||
v-b-tooltip.hover
|
||||
:class="{'active': drawer.panel == 'lyrics'}"
|
||||
@click="invokeDrawer('lyrics')"></button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button class="playback-button--small lyrics"
|
||||
:style="{'opacity': 0.3, 'pointer-events': 'none'}"></button>
|
||||
</template>
|
||||
</div>
|
||||
<template v-if="lyrics && lyrics != [] && lyrics.length > 0">
|
||||
<button class="playback-button--small lyrics"
|
||||
:title="$root.getLz('term.lyrics')"
|
||||
v-b-tooltip.hover
|
||||
:class="{'active': drawer.panel == 'lyrics'}"
|
||||
@click="invokeDrawer('lyrics')"></button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button class="playback-button--small lyrics"
|
||||
:style="{'opacity': 0.3, 'pointer-events': 'none'}"></button>
|
||||
</template>
|
||||
</div>
|
||||
<div id="player-pip"
|
||||
@click="pip()"
|
||||
title="Picture-in-Picture"
|
||||
v-b-tooltip.hover>
|
||||
@click="pip()"
|
||||
title="Picture-in-Picture"
|
||||
v-b-tooltip.hover>
|
||||
<%- include("../svg/pip.svg") %>
|
||||
</div>
|
||||
<div id="player-fullscreen"
|
||||
@click="fullscreen(!fullscreenState)"
|
||||
title="Fullscreen"
|
||||
v-b-tooltip.hover>
|
||||
@click="fullscreen(!fullscreenState)"
|
||||
title="Fullscreen"
|
||||
v-b-tooltip.hover>
|
||||
<%- include("../svg/fullscreen.svg") %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,88 +1,90 @@
|
|||
<script type="text/x-template" id="add-to-playlist">
|
||||
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.resetState()" @contextmenu.self="app.resetState()">
|
||||
<div class="modal-window">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">{{app.getLz('action.addToPlaylist')}}</div>
|
||||
<button class="close-btn" @click="app.resetState()" :aria-label="app.getLz('action.close')"></button>
|
||||
</div>
|
||||
<div class="modal-content">
|
||||
<button class="playlist-item"
|
||||
@click="app.addSelectedToNewPlaylist()" style="width:100%;">
|
||||
<div class="icon"><%- include("../svg/plus.svg") %></div>
|
||||
<div class="name">{{app.getLz('action.createPlaylist')}}</div>
|
||||
</button>
|
||||
<sidebar-playlist :playlist-select="playlistSelect" :relate-media-items="relateItems" v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')" :item="item">
|
||||
</sidebar-playlist>
|
||||
</div>
|
||||
<div class="modal-search">
|
||||
<div class="search-input-container" style="width:100%;margin: 16px 0;">
|
||||
<div class="search-input--icon"></div>
|
||||
<input type="search"
|
||||
ref="searchInput"
|
||||
style="width:100%;"
|
||||
spellcheck="false"
|
||||
:placeholder="app.getLz('term.search') + '...'"
|
||||
v-model="searchQuery"
|
||||
@input="search()"
|
||||
class="search-input">
|
||||
</div>
|
||||
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.resetState()"
|
||||
@contextmenu.self="app.resetState()">
|
||||
<div class="modal-window">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">{{app.getLz('action.addToPlaylist')}}</div>
|
||||
<button class="close-btn" @click="app.resetState()" :aria-label="app.getLz('action.close')"></button>
|
||||
</div>
|
||||
<div class="modal-content">
|
||||
<button class="playlist-item"
|
||||
@click="app.addSelectedToNewPlaylist()" style="width:100%;">
|
||||
<div class="icon"><%- include("../svg/plus.svg") %></div>
|
||||
<div class="name">{{app.getLz('action.createPlaylist')}}</div>
|
||||
</button>
|
||||
<sidebar-playlist :playlist-select="playlistSelect" :relate-media-items="relateItems"
|
||||
v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')" :item="item">
|
||||
</sidebar-playlist>
|
||||
</div>
|
||||
<div class="modal-search">
|
||||
<div class="search-input-container" style="width:100%;margin: 16px 0;">
|
||||
<div class="search-input--icon"></div>
|
||||
<input type="search"
|
||||
ref="searchInput"
|
||||
style="width:100%;"
|
||||
spellcheck="false"
|
||||
:placeholder="app.getLz('term.search') + '...'"
|
||||
v-model="searchQuery"
|
||||
@input="search()"
|
||||
class="search-input">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
Vue.component('add-to-playlist', {
|
||||
template: '#add-to-playlist',
|
||||
data: function () {
|
||||
return {
|
||||
playlistSorted: [],
|
||||
searchQuery: "",
|
||||
focused: "",
|
||||
app: this.$root,
|
||||
relateItems: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
playlists: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.relateItems = [this.$root.selectedMediaItems[0].id]
|
||||
this.search()
|
||||
this.$refs.searchInput.focus()
|
||||
this.$refs.searchInput.addEventListener('keydown', (e) => {
|
||||
if (e.keyCode == 13) {
|
||||
if (this.focused != "") {
|
||||
this.addToPlaylist(this.focused)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
playlistSelect(playlist) {
|
||||
if (playlist.type != "library-playlist-folders") {
|
||||
this.addToPlaylist(playlist.id)
|
||||
}
|
||||
},
|
||||
addToPlaylist(id) {
|
||||
app.addSelectedToPlaylist(id)
|
||||
},
|
||||
search() {
|
||||
this.focused = ""
|
||||
if (this.searchQuery == "") {
|
||||
this.playlistSorted = this.playlists
|
||||
} else {
|
||||
this.playlistSorted = this.playlists.filter(playlist => {
|
||||
return playlist.attributes.name.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1
|
||||
})
|
||||
if (this.playlistSorted.length == 1) {
|
||||
this.focused = this.playlistSorted[0].id
|
||||
}
|
||||
}
|
||||
},
|
||||
Vue.component('add-to-playlist', {
|
||||
template: '#add-to-playlist',
|
||||
data: function() {
|
||||
return {
|
||||
playlistSorted: [],
|
||||
searchQuery: "",
|
||||
focused: "",
|
||||
app: this.$root,
|
||||
relateItems: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
playlists: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.relateItems = [this.$root.selectedMediaItems[0].id]
|
||||
this.search()
|
||||
this.$refs.searchInput.focus()
|
||||
this.$refs.searchInput.addEventListener('keydown', (e) => {
|
||||
if (e.keyCode == 13) {
|
||||
if (this.focused != "") {
|
||||
this.addToPlaylist(this.focused)
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
playlistSelect(playlist) {
|
||||
if (playlist.type != "library-playlist-folders") {
|
||||
this.addToPlaylist(playlist.id)
|
||||
}
|
||||
},
|
||||
addToPlaylist(id) {
|
||||
app.addSelectedToPlaylist(id)
|
||||
},
|
||||
search() {
|
||||
this.focused = ""
|
||||
if (this.searchQuery == "") {
|
||||
this.playlistSorted = this.playlists
|
||||
} else {
|
||||
this.playlistSorted = this.playlists.filter(playlist => {
|
||||
return playlist.attributes.name.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1
|
||||
})
|
||||
if (this.playlistSorted.length == 1) {
|
||||
this.focused = this.playlistSorted[0].id
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,42 +1,45 @@
|
|||
<script type="text/x-template" id="airplay-modal">
|
||||
<div class="spatialproperties-panel castmenu modal-fullscreen airplay-modal" @click.self="close()" @contextmenu.self="close()">
|
||||
<div class="modal-window airplay-modal">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">{{'Enter password'}}</div>
|
||||
<button class="close-btn" @click="close()" :aria-label="this.$root.getLz('action.close')"></button>
|
||||
</div>
|
||||
<div class="modal-content" style="overflow-y: overlay; padding: 3%">
|
||||
<input type="text" v-model="passcode"/>
|
||||
</div>
|
||||
<div class="md-footer">
|
||||
<div class="row">
|
||||
<div class="col" >
|
||||
<button style="width:100%" @click="enterPassword()" class="md-btn md-btn-block md-btn-primary">{{'OK'}}</button>
|
||||
<div class="spatialproperties-panel castmenu modal-fullscreen airplay-modal" @click.self="close()"
|
||||
@contextmenu.self="close()">
|
||||
<div class="modal-window airplay-modal">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">{{'Enter password'}}</div>
|
||||
<button class="close-btn" @click="close()" :aria-label="this.$root.getLz('action.close')"></button>
|
||||
</div>
|
||||
<div class="modal-content" style="overflow-y: overlay; padding: 3%">
|
||||
<input type="text" v-model="passcode" />
|
||||
</div>
|
||||
<div class="md-footer">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<button style="width:100%" @click="enterPassword()" class="md-btn md-btn-block md-btn-primary">
|
||||
{{'OK'}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
<script>
|
||||
Vue.component('airplay-modal', {
|
||||
template: '#airplay-modal',
|
||||
data: function () {
|
||||
return {
|
||||
passcode: '',
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.$root.modals.airplayPW = false
|
||||
},
|
||||
enterPassword() {
|
||||
console.log('Entered passCode: ', this.passcode)
|
||||
ipcRenderer.send("setAirPlayPasscode",this.passcode)
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
Vue.component('airplay-modal', {
|
||||
template: '#airplay-modal',
|
||||
data: function() {
|
||||
return {
|
||||
passcode: '',
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.$root.modals.airplayPW = false
|
||||
},
|
||||
enterPassword() {
|
||||
console.log('Entered passCode: ', this.passcode)
|
||||
ipcRenderer.send("setAirPlayPasscode", this.passcode)
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -5,93 +5,98 @@
|
|||
</script>
|
||||
|
||||
<script>
|
||||
Vue.component('animatedartwork-view', {
|
||||
template: '#animatedartwork-view',
|
||||
data: function () {
|
||||
return {
|
||||
app: this.$root,
|
||||
hls: null,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
video: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
priority: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
if (!this.priority && app.cfg.visual.animated_artwork == "limited") {
|
||||
return
|
||||
} else if (app.cfg.visual.animated_artwork == "disabled") {
|
||||
return
|
||||
}
|
||||
if (this.video) {
|
||||
this.$nextTick(function () {
|
||||
var config = {
|
||||
backBufferLength: 0,
|
||||
enableWebVTT: false,
|
||||
enableCEA708Captions: false,
|
||||
emeEnabled: false,
|
||||
abrEwmaDefaultEstimate: 10000,
|
||||
testBandwidth: false,
|
||||
};
|
||||
if (this.hls) {
|
||||
this.hls.detachMedia();
|
||||
} else {
|
||||
Vue.component('animatedartwork-view', {
|
||||
template: '#animatedartwork-view',
|
||||
data: function() {
|
||||
return {
|
||||
app: this.$root,
|
||||
hls: null,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
video: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
priority: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
if (!this.priority && app.cfg.visual.animated_artwork == "limited") {
|
||||
return
|
||||
} else if (app.cfg.visual.animated_artwork == "disabled") {
|
||||
return
|
||||
}
|
||||
if (this.video) {
|
||||
this.$nextTick(function() {
|
||||
var config = {
|
||||
backBufferLength: 0,
|
||||
enableWebVTT: false,
|
||||
enableCEA708Captions: false,
|
||||
emeEnabled: false,
|
||||
abrEwmaDefaultEstimate: 10000,
|
||||
testBandwidth: false,
|
||||
};
|
||||
if (this.hls) {
|
||||
this.hls.detachMedia();
|
||||
} else {
|
||||
|
||||
this.hls = new CiderHls(config);
|
||||
}
|
||||
// bind them together
|
||||
if (this.$refs.video) {
|
||||
let d = "WIDEVINE_SOFTWARE"
|
||||
let h = {
|
||||
initDataTypes: ["cenc", "keyids"],
|
||||
distinctiveIdentifier: "optional",
|
||||
persistentState: "required"
|
||||
}
|
||||
let p = {
|
||||
platformInfo: {requiresCDMAttachOnStart: !0, maxSecurityLevel: d, keySystemConfig: h},
|
||||
appData: {serviceName: "Apple Music"}
|
||||
}
|
||||
|
||||
this.hls.attachMedia(this.$refs.video);
|
||||
this.hls.loadSource(this.video, p);
|
||||
let u = this.hls;
|
||||
var quality = app.cfg.visual.animated_artwork_qualityLevel;
|
||||
setTimeout(() => {
|
||||
let levelsnum = u.levels.map((level)=>{return level.width})
|
||||
if (levelsnum.length > 0) {
|
||||
let qualities = []
|
||||
let qualities2 = []
|
||||
for (let i = 0; i < levelsnum.length; i++) {
|
||||
if (qualities2.indexOf(levelsnum[i]) == -1) {
|
||||
qualities.push({level: i, quality: levelsnum[i]})
|
||||
qualities2.push(levelsnum[i])
|
||||
}
|
||||
}
|
||||
let actualnum = Math.floor(qualities[qualities.length -1 ].level * (quality / 4))
|
||||
if (quality != 0 ){
|
||||
quality = qualities[Math.min(actualnum,qualities.length -1 )].level
|
||||
}
|
||||
if (quality == 4 ){
|
||||
quality = qualities[qualities.length - 1].level
|
||||
}
|
||||
}
|
||||
try{
|
||||
this.hls.loadLevel = parseInt( quality || 1);} catch(e){}},200)
|
||||
}
|
||||
})
|
||||
this.hls = new CiderHls(config);
|
||||
}
|
||||
// bind them together
|
||||
if (this.$refs.video) {
|
||||
let d = "WIDEVINE_SOFTWARE"
|
||||
let h = {
|
||||
initDataTypes: ["cenc", "keyids"],
|
||||
distinctiveIdentifier: "optional",
|
||||
persistentState: "required"
|
||||
}
|
||||
},
|
||||
async beforeDestroy() {
|
||||
if (this.hls) {
|
||||
await this.hls.destroy();
|
||||
this.hls = null
|
||||
let p = {
|
||||
platformInfo: { requiresCDMAttachOnStart: !0, maxSecurityLevel: d, keySystemConfig: h },
|
||||
appData: { serviceName: "Apple Music" }
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
this.hls.attachMedia(this.$refs.video);
|
||||
this.hls.loadSource(this.video, p);
|
||||
let u = this.hls;
|
||||
var quality = app.cfg.visual.animated_artwork_qualityLevel;
|
||||
setTimeout(() => {
|
||||
let levelsnum = u.levels.map((level) => {
|
||||
return level.width
|
||||
})
|
||||
if (levelsnum.length > 0) {
|
||||
let qualities = []
|
||||
let qualities2 = []
|
||||
for (let i = 0; i < levelsnum.length; i++) {
|
||||
if (qualities2.indexOf(levelsnum[i]) == -1) {
|
||||
qualities.push({ level: i, quality: levelsnum[i] })
|
||||
qualities2.push(levelsnum[i])
|
||||
}
|
||||
}
|
||||
let actualnum = Math.floor(qualities[qualities.length - 1].level * (quality / 4))
|
||||
if (quality != 0) {
|
||||
quality = qualities[Math.min(actualnum, qualities.length - 1)].level
|
||||
}
|
||||
if (quality == 4) {
|
||||
quality = qualities[qualities.length - 1].level
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.hls.loadLevel = parseInt(quality || 1);
|
||||
} catch (e) {
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
async beforeDestroy() {
|
||||
if (this.hls) {
|
||||
await this.hls.destroy();
|
||||
this.hls = null
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -15,14 +15,14 @@
|
|||
<transition
|
||||
<% if(env.appRoutes[i].onEnter) {
|
||||
%>
|
||||
v-on:enter="<%- env.appRoutes[i].onEnter %>"
|
||||
v-on:enter="<%- env.appRoutes[i].onEnter; %>"
|
||||
<%
|
||||
}
|
||||
%>
|
||||
:name="$root.chrome.desiredPageTransition">
|
||||
<template
|
||||
v-if="<%- env.appRoutes[i].condition %>">
|
||||
<%- env.appRoutes[i].component %>
|
||||
v-if="<%- env.appRoutes[i].condition; %>">
|
||||
<%- env.appRoutes[i].component; %>
|
||||
</template>
|
||||
</transition>
|
||||
<% } %>
|
||||
|
@ -38,14 +38,13 @@
|
|||
</script>
|
||||
|
||||
<script>
|
||||
Vue.component('app-content-area', {
|
||||
template: '#app-content-area',
|
||||
data: function () {
|
||||
return {
|
||||
scrollPos: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
Vue.component('app-content-area', {
|
||||
template: '#app-content-area',
|
||||
data: function() {
|
||||
return {
|
||||
scrollPos: 0
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
});
|
||||
</script>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue