CHONKY BOY

This commit is contained in:
Core 2022-08-04 05:27:29 +01:00
parent 31ed921a1a
commit c15f55d0ee
No known key found for this signature in database
GPG key ID: FE9BF1B547F8F3C6
213 changed files with 64188 additions and 55736 deletions

View file

@ -1 +1,2 @@
bracketSameLine: true bracketSameLine: true
printWidth: 240

View file

@ -1,62 +1,62 @@
let i = 1, k = 1; let i = 1,
k = 1;
class ExamplePlugin { class ExamplePlugin {
/** /**
* Private variables for interaction in plugins * Private variables for interaction in plugins
*/ */
private _win: any; private _win: any;
private _app: any; private _app: any;
private _store: any; private _store: any;
/** /**
* Base Plugin Details (Eventually implemented into a GUI in settings) * Base Plugin Details (Eventually implemented into a GUI in settings)
*/ */
public name: string = 'examplePlugin'; public name: string = "examplePlugin";
public description: string = 'Example plugin'; public description: string = "Example plugin";
public version: string = '1.0.0'; public version: string = "1.0.0";
public author: string = 'Example author'; public author: string = "Example author";
/** /**
* Runs on plugin load (Currently run on application start) * Runs on plugin load (Currently run on application start)
*/ */
constructor(app: any, store: any) { constructor(app: any, store: any) {
this._app = app; this._app = app;
this._store = store; this._store = store;
console.debug(`[Plugin][${this.name}] Loading Complete.`); console.debug(`[Plugin][${this.name}] Loading Complete.`);
} }
/** /**
* Runs on app ready * Runs on app ready
*/ */
onReady(win: any): void { onReady(win: any): void {
this._win = win; this._win = win;
console.debug(`[Plugin][${this.name}] Ready.`); console.debug(`[Plugin][${this.name}] Ready.`);
} }
/** /**
* Runs on app stop * Runs on app stop
*/ */
onBeforeQuit(): void { onBeforeQuit(): void {
console.debug(`[Plugin][${this.name}] Stopped.`); console.debug(`[Plugin][${this.name}] Stopped.`);
} }
/** /**
* Runs on playback State Change * Runs on playback State Change
* @param attributes Music Attributes (attributes.status = current state) * @param attributes Music Attributes (attributes.status = current state)
*/ */
onPlaybackStateDidChange(attributes: object): void { onPlaybackStateDidChange(attributes: object): void {
console.log('onPlaybackStateDidChange has been called ' + i + ' times'); console.log("onPlaybackStateDidChange has been called " + i + " times");
i++ i++;
} }
/**
* Runs on song change
* @param attributes Music Attributes
*/
onNowPlayingItemDidChange(attributes: object): void {
console.log('onNowPlayingDidChange has been called ' + k + ' times');
k++
}
/**
* 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;

View file

@ -1,39 +1,39 @@
class sendSongToTitlebar { class sendSongToTitlebar {
/** /**
* Base Plugin Details (Eventually implemented into a GUI in settings) * Base Plugin Details (Eventually implemented into a GUI in settings)
*/ */
public name: string = 'sendSongToTitlebar'; public name: string = "sendSongToTitlebar";
public description: string = 'Sets the app\'s titlebar to the Song title'; public description: string = "Sets the app's titlebar to the Song title";
public version: string = '0.0.1'; public version: string = "0.0.1";
public author: string = 'Cider Collective (credit to 8times9 via #147)'; public author: string = "Cider Collective (credit to 8times9 via #147)";
/** /**
* Runs on plugin load (Currently run on application start) * Runs on plugin load (Currently run on application start)
*/ */
private _win: any; private _win: any;
private _app: any; private _app: any;
constructor() {} constructor() {}
/** /**
* Runs on app ready * Runs on app ready
*/ */
onReady(win: any): void { onReady(win: any): void {
this._win = win; this._win = win;
} }
/** /**
* Runs on app stop * Runs on app stop
*/ */
onBeforeQuit(): void {} onBeforeQuit(): void {}
/** /**
* Runs on playback State Change * Runs on playback State Change
* @param attributes Music Attributes (attributes.status = current state) * @param attributes Music Attributes (attributes.status = current state)
*/ */
onPlaybackStateDidChange(attributes: any): void { onPlaybackStateDidChange(attributes: any): void {
this._win.setTitle(`${(attributes != null && attributes.name != null && attributes.name.length > 0) ? (attributes.name + " - ") : ''}Cider`) this._win.setTitle(`${attributes != null && attributes.name != null && attributes.name.length > 0 ? attributes.name + " - " : ""}Cider`);
} }
/** /**
* Runs on song change * Runs on song change
* @param attributes Music Attributes * @param attributes Music Attributes
*/ */
onNowPlayingItemDidChange(attributes: object): void {} onNowPlayingItemDidChange(attributes: object): void {}
} }
module.exports = sendSongToTitlebar; module.exports = sendSongToTitlebar;

View file

@ -1,4 +1,4 @@
{ {
"only-arches": ["x86_64"], "only-arches": ["x86_64"],
"publish-delay-hours": 2 "publish-delay-hours": 2
} }

View file

@ -6,31 +6,15 @@
}, },
"appId": "cider", "appId": "cider",
"protocols": [ "protocols": [
{ {
"name": "Cider", "name": "Cider",
"schemes": [ "schemes": ["ame", "cider", "itms", "itmss", "musics", "music"]
"ame",
"cider",
"itms",
"itmss",
"musics",
"music"
]
} }
], ],
"extends": null, "extends": null,
"files": [ "files": ["**/*", "./src/**/*", "./resources/icons/icon.*"],
"**/*",
"./src/**/*",
"./resources/icons/icon.*"
],
"linux": { "linux": {
"target": [ "target": ["AppImage", "deb", "snap", "rpm"],
"AppImage",
"deb",
"snap",
"rpm"
],
"synopsis": "A new look into listening and enjoying music in style and performance. ", "synopsis": "A new look into listening and enjoying music in style and performance. ",
"category": "AudioVideo", "category": "AudioVideo",
"icon": "cider", "icon": "cider",
@ -45,9 +29,7 @@
"setBuildNumber": true "setBuildNumber": true
}, },
"win": { "win": {
"target": [ "target": ["appx"],
"appx"
],
"icon": "resources/icons/icon.ico" "icon": "resources/icons/icon.ico"
}, },
"directories": { "directories": {
@ -60,4 +42,4 @@
"entitlements": "resources/entitlements.mac.plist", "entitlements": "resources/entitlements.mac.plist",
"darkModeSupport": true "darkModeSupport": true
} }
} }

View file

@ -35,51 +35,48 @@
"msft": "yarn build && electron-builder -c msft-package.json", "msft": "yarn build && electron-builder -c msft-package.json",
"mstest": "yarn build && electron-builder -c msft-test.json", "mstest": "yarn build && electron-builder -c msft-test.json",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"prettier": "npx prettier --write '**/*.{js,json,ts}'" "prettier": "npx prettier --write '**/*.{js,json,ts,css,less}'"
}, },
"dependencies": { "dependencies": {
"@sentry/electron": "^3.0.7", "@sentry/electron": "^3.0.7",
"@sentry/integrations": "^6.19.6", "@sentry/integrations": "^7.8.1",
"@types/pouchdb": "^6.4.0", "@types/pouchdb": "^6.4.0",
"@types/pouchdb-node": "^6.1.4", "@types/pouchdb-node": "^6.1.4",
"adm-zip": "0.4.10", "adm-zip": "0.4.10",
"airtunes2": "git+https://github.com/ciderapp/node_airtunes2.git", "airtunes2": "git+https://github.com/ciderapp/node_airtunes2.git",
"castv2-client": "^1.2.0", "castv2-client": "^1.2.0",
"chokidar": "^3.5.3", "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", "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-fetch": "^1.7.4",
"electron-log": "^4.4.6", "electron-log": "^4.4.8",
"electron-notarize": "^1.2.1", "electron-notarize": "^1.2.1",
"electron-store": "^8.0.1", "electron-store": "^8.1.0",
"electron-updater": "^5.0.1", "electron-updater": "^5.2.1",
"electron-window-state": "^5.0.3", "electron-window-state": "^5.0.3",
"express": "^4.17.3", "express": "^4.18.1",
"get-port": "^5.1.1", "get-port": "^5.1.1",
"jimp": "^0.16.1", "jimp": "^0.16.1",
"jsonc": "^2.0.0",
"lastfmapi": "^0.1.1", "lastfmapi": "^0.1.1",
"level": "^8.0.0", "level": "^8.0.0",
"leveldown": "^6.1.1", "leveldown": "^6.1.1",
"mdns-js": "git+https://github.com/ciderapp/node-mdns-js.git", "mdns-js": "git+https://github.com/ciderapp/node-mdns-js.git",
"mpris-service": "^2.1.2", "mpris-service": "^2.1.2",
"music-metadata": "^7.12.4", "music-metadata": "^7.12.5",
"node-gyp": "^9.0.0", "node-gyp": "^9.1.0",
"node-ssdp": "^4.0.1", "node-ssdp": "^4.0.1",
"pouchdb-adapter-leveldb": "^7.3.0", "pouchdb-adapter-leveldb": "^7.3.0",
"pouchdb-node": "^7.3.0", "pouchdb-node": "^7.3.0",
"pouchdb-upsert": "^2.2.0", "pouchdb-upsert": "^2.2.0",
"qrcode": "^1.5.0", "qrcode": "^1.5.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"run-script-os": "^1.1.6",
"request": "^2.88.2", "request": "^2.88.2",
"run-script-os": "^1.1.6",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"ts-md5": "^1.2.11", "ts-md5": "^1.2.11",
"v8-compile-cache": "^2.3.0", "v8-compile-cache": "^2.3.0",
"wallpaper": "5.0.1", "wallpaper": "5.0.1",
"ws": "^8.5.0", "ws": "^8.8.1",
"xml2js": "^0.4.23", "xml2js": "^0.4.23",
"youtube-search-without-api-key": "^1.0.7" "youtube-search-without-api-key": "^1.0.7"
}, },
@ -90,14 +87,14 @@
"@types/qrcode-terminal": "^0.12.0", "@types/qrcode-terminal": "^0.12.0",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"electron": "git+https://github.com/castlabs/electron-releases.git#18-x-y", "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-builder-notarize-pkg": "^1.2.0",
"electron-webpack": "^2.8.2", "electron-webpack": "^2.8.2",
"less": "^4.1.3", "less": "^4.1.3",
"musickit-typescript": "^1.2.4", "musickit-typescript": "^1.2.4",
"typescript": "^4.6.4", "typescript": "^4.7.4",
"vue-devtools": "^5.1.4", "vue-devtools": "^5.1.4",
"webpack": "~5.72.0" "webpack": "~5.74.0"
}, },
"fileAssociations": [ "fileAssociations": [
{ {

View file

@ -1,30 +1,35 @@
exports.default = function(context) { exports.default = function (context) {
const { execSync } = require('child_process') const { execSync } = require("child_process");
const fs = require('fs') const fs = require("fs");
if (process.platform !== 'darwin') if (process.platform !== "darwin") return;
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')
// xcode 13 if (fs.existsSync("dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig"))
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')) fs.unlinkSync("dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig");
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--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') // xcode 13
// if (fs.existsSync('dist/mac-universal')) if (
// execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac-universal',{stdio: 'inherit'}) fs.existsSync("dist/mac-universal--x64") &&
// if (fs.existsSync('dist/mac')) fs.existsSync("dist/mac-universal--arm64") &&
// execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac',{stdio: 'inherit'}) fs.existsSync("dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib/keyedobjects-101300.nib")
// if (fs.existsSync('dist/mac-arm64')) )
// execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac-arm64 -z',{stdio: 'inherit'}) 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')
};

View file

@ -1,11 +1,9 @@
{ {
"$schema": "https://github.com/argv-minus-one/dmg-license/raw/master/schema.json", "$schema": "https://github.com/argv-minus-one/dmg-license/raw/master/schema.json",
"body": [ "body": [
{ {
"file": "license.txt", "file": "license.txt",
"lang": ["en-US"] "lang": ["en-US"]
} }
] ]
}
}

View file

@ -19,381 +19,390 @@ const macosVersion_1 = require("./util/macosVersion");
const pathManager_1 = require("./util/pathManager"); const pathManager_1 = require("./util/pathManager");
const fs = require("fs/promises"); const fs = require("fs/promises");
class MacPackager extends platformPackager_1.PlatformPackager { class MacPackager extends platformPackager_1.PlatformPackager {
constructor(info) { constructor(info) {
super(info, core_1.Platform.MAC); super(info, core_1.Platform.MAC);
this.codeSigningInfo = new lazy_val_1.Lazy(() => { this.codeSigningInfo = new lazy_val_1.Lazy(() => {
const cscLink = this.getCscLink(); const cscLink = this.getCscLink();
if (cscLink == null || process.platform !== "darwin") { if (cscLink == null || process.platform !== "darwin") {
return Promise.resolve({ keychainFile: process.env.CSC_KEYCHAIN || null }); 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;
});
}); });
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) { async pack(outDir, arch, targets, taskManager) {
return new appInfo_1.AppInfo(this.info, this.platformSpecificBuildOptions.bundleVersion, this.platformSpecificBuildOptions); 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() { for (const target of targets) {
return this._iconPath.value; 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) { if (nonMasPromise != null) {
for (const name of targets) { await nonMasPromise;
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;
}
}
} }
async doPack(outDir, appOutDir, platformName, arch, platformSpecificBuildOptions, targets) { }
switch (arch) { async sign(appPath, outDir, masOptions, arch) {
default: { if (!macCodeSign_1.isSignAllowed()) {
return super.doPack(outDir, appOutDir, platformName, arch, platformSpecificBuildOptions, targets); return;
}
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 pack(outDir, arch, targets, taskManager) { const isMas = masOptions != null;
let nonMasPromise = null; const options = masOptions == null ? this.platformSpecificBuildOptions : masOptions;
const hasMas = targets.length !== 0 && targets.some(it => it.name === "mas" || it.name === "mas-dev"); const qualifier = options.identity;
const prepackaged = this.packagerOptions.prepackaged; if (!isMas && qualifier === null) {
if (!hasMas || targets.length > 1) { if (this.forceCodeSigning) {
const appPath = prepackaged == null ? path.join(this.computeAppOutDir(outDir, arch), `${this.appInfo.productFilename}.app`) : prepackaged; throw new builder_util_1.InvalidConfigurationError("identity explicitly is set to null, but forceCodeSigning is set to true");
nonMasPromise = (prepackaged }
? Promise.resolve() builder_util_1.log.info({ reason: "identity explicitly is set to null" }, "skipped macOS code signing");
: this.doPack(outDir, path.dirname(appPath), this.platform.nodeName, arch, this.platformSpecificBuildOptions, targets)).then(() => this.packageInDistributableFormat(appPath, arch, targets, taskManager)); return;
}
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;
}
} }
async sign(appPath, outDir, masOptions, arch) { const keychainFile = (await this.codeSigningInfo.value).keychainFile;
if (!macCodeSign_1.isSignAllowed()) { const explicitType = options.type;
return; 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; if (identity == null) {
const qualifier = options.identity; await macCodeSign_1.reportError(isMas, certificateTypes, qualifier, keychainFile, this.forceCodeSigning);
if (!isMas && qualifier === null) { return;
if (this.forceCodeSigning) { }
throw new builder_util_1.InvalidConfigurationError("identity explicitly is set to null, but forceCodeSigning is set to true"); }
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; return (
const explicitType = options.type; file.endsWith(".kext") ||
const type = explicitType || "distribution"; file.startsWith("/Contents/PlugIns", appPath.length) ||
const isDevelopment = type === "development"; file.includes("/node_modules/puppeteer/.local-chromium") ||
const certificateTypes = getCertificateTypes(isMas, isDevelopment); file.includes("/node_modules/playwright-firefox/.local-browsers") ||
let identity = null; file.includes("/node_modules/playwright/.local-browsers")
for (const certificateType of certificateTypes) { );
identity = await macCodeSign_1.findIdentity(certificateType, qualifier, keychainFile); /* Those are browser automating modules, browser (chromium, nightly) cannot be signed
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
https://github.com/electron-userland/electron-builder/issues/2010 https://github.com/electron-userland/electron-builder/issues/2010
https://github.com/electron-userland/electron-builder/issues/5383 https://github.com/electron-userland/electron-builder/issues/5383
*/ */
}, },
identity: identity, identity: identity,
type, type,
platform: isMas ? "mas" : "darwin", platform: isMas ? "mas" : "darwin",
version: this.config.electronVersion, version: this.config.electronVersion,
app: appPath, app: appPath,
keychain: keychainFile || undefined, keychain: keychainFile || undefined,
binaries, binaries,
timestamp: isMas ? masOptions === null || masOptions === void 0 ? void 0 : masOptions.timestamp : options.timestamp, 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), requirements: isMas || this.platformSpecificBuildOptions.requirements == null ? undefined : await this.getResource(this.platformSpecificBuildOptions.requirements),
// https://github.com/electron-userland/electron-osx-sign/issues/196 // 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. // will fail on 10.14.5+ because a signed but unnotarized app is also rejected.
"gatekeeper-assess": options.gatekeeperAssess === true, "gatekeeper-assess": options.gatekeeperAssess === true,
// https://github.com/electron-userland/electron-builder/issues/1480 // https://github.com/electron-userland/electron-builder/issues/1480
"strict-verify": options.strictVerify, "strict-verify": options.strictVerify,
hardenedRuntime: isMas ? masOptions && masOptions.hardenedRuntime === true : options.hardenedRuntime !== false, hardenedRuntime: isMas ? masOptions && masOptions.hardenedRuntime === true : options.hardenedRuntime !== false,
}; };
await this.adjustSignOptions(signOptions, masOptions); await this.adjustSignOptions(signOptions, masOptions);
builder_util_1.log.info({ builder_util_1.log.info(
file: builder_util_1.log.filePath(appPath), {
identityName: identity.name, file: builder_util_1.log.filePath(appPath),
identityHash: identity.hash, identityName: identity.name,
provisioningProfile: signOptions["provisioning-profile"] || "none", identityHash: identity.hash,
}, "signing"); provisioningProfile: signOptions["provisioning-profile"] || "none",
await this.doSign(signOptions); },
// https://github.com/electron-userland/electron-builder/issues/1196#issuecomment-312310209 "signing"
if (masOptions != null && !isDevelopment) { );
const certType = isDevelopment ? "Mac Developer" : "3rd Party Mac Developer Installer"; await this.doSign(signOptions);
const masInstallerIdentity = await macCodeSign_1.findIdentity(certType, masOptions.identity, keychainFile); // https://github.com/electron-userland/electron-builder/issues/1196#issuecomment-312310209
if (masInstallerIdentity == null) { if (masOptions != null && !isDevelopment) {
throw new builder_util_1.InvalidConfigurationError(`Cannot find valid "${certType}" identity to sign MAS installer, please see https://electron.build/code-signing`); const certType = isDevelopment ? "Mac Developer" : "3rd Party Mac Developer Installer";
} const masInstallerIdentity = await macCodeSign_1.findIdentity(certType, masOptions.identity, keychainFile);
// mas uploaded to AppStore, so, use "-" instead of space for name if (masInstallerIdentity == null) {
const artifactName = this.expandArtifactNamePattern(masOptions, "pkg", arch); throw new builder_util_1.InvalidConfigurationError(`Cannot find valid "${certType}" identity to sign MAS installer, please see https://electron.build/code-signing`);
const artifactPath = path.join(outDir, artifactName); }
await this.doFlat(appPath, artifactPath, masInstallerIdentity, keychainFile); // mas uploaded to AppStore, so, use "-" instead of space for name
await this.dispatchArtifactCreated(artifactPath, null, builder_util_1.Arch.x64, this.computeSafeArtifactName(artifactName, "pkg", arch, true, this.platformSpecificBuildOptions.defaultArch)); 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; async adjustSignOptions(signOptions, masOptions) {
const customSignOptions = masOptions || this.platformSpecificBuildOptions; const resourceList = await this.resourceList;
const entitlementsSuffix = masOptions == null ? "mac" : "mas"; const customSignOptions = masOptions || this.platformSpecificBuildOptions;
let entitlements = customSignOptions.entitlements; const entitlementsSuffix = masOptions == null ? "mac" : "mas";
if (entitlements == null) { let entitlements = customSignOptions.entitlements;
const p = `entitlements.${entitlementsSuffix}.plist`; if (entitlements == null) {
if (resourceList.includes(p)) { const p = `entitlements.${entitlementsSuffix}.plist`;
entitlements = path.join(this.info.buildResourcesDir, p); if (resourceList.includes(p)) {
} entitlements = path.join(this.info.buildResourcesDir, p);
else { } else {
entitlements = pathManager_1.getTemplatePath("entitlements.mac.plist"); 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;
} }
//noinspection JSMethodCanBeStatic signOptions.entitlements = entitlements;
async doSign(opts) { let entitlementsInherit = customSignOptions.entitlementsInherit;
return electron_osx_sign_1.signAsync(opts); 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 signOptions["entitlements-inherit"] = entitlementsInherit;
async doFlat(appPath, outFile, identity, keychain) { if (customSignOptions.provisioningProfile != null) {
// productbuild doesn't created directory for out file signOptions["provisioning-profile"] = customSignOptions.provisioningProfile;
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) { signOptions["entitlements-loginhelper"] = customSignOptions.entitlementsLoginHelper;
return path.resolve(this.projectDir, dist, this.info.framework.distMacOsAppName); }
//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) { appPlist.CFBundleName = appInfo.productName;
return path.join(appOutDir, this.info.framework.distMacOsAppName); appPlist.CFBundleDisplayName = appInfo.productName;
const minimumSystemVersion = this.platformSpecificBuildOptions.minimumSystemVersion;
if (minimumSystemVersion != null) {
appPlist.LSMinimumSystemVersion = minimumSystemVersion;
} }
// todo fileAssociations appPlist.CFBundleIdentifier = appInfo.macBundleIdentifier;
async applyCommonInfo(appPlist, contentsPath) { appPlist.CFBundleShortVersionString = this.platformSpecificBuildOptions.bundleShortVersion || appInfo.version;
const appInfo = this.appInfo; appPlist.CFBundleVersion = appInfo.buildVersion;
const appFilename = appInfo.productFilename; builder_util_1.use(this.platformSpecificBuildOptions.category || this.config.category, (it) => (appPlist.LSApplicationCategoryType = it));
// https://github.com/electron-userland/electron-builder/issues/1278 appPlist.NSHumanReadableCopyright = appInfo.copyright;
appPlist.CFBundleExecutable = appFilename.endsWith(" Helper") ? appFilename.substring(0, appFilename.length - " Helper".length) : appFilename; if (this.platformSpecificBuildOptions.darkModeSupport) {
const icon = await this.getIconPath(); appPlist.NSRequiresAquaSystemAppearance = false;
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);
}
} }
async signApp(packContext, isAsar) { const extendInfo = this.platformSpecificBuildOptions.extendInfo;
const appFileName = `${this.appInfo.productFilename}.app`; if (extendInfo != null) {
await bluebird_lst_1.default.map(promises_1.readdir(packContext.appOutDir), (file) => { Object.assign(appPlist, extendInfo);
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;
}
});
} }
}
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; exports.default = MacPackager;
function getCertificateTypes(isMas, isDevelopment) { function getCertificateTypes(isMas, isDevelopment) {
if (isDevelopment) { if (isDevelopment) {
return isMas ? ["Mac Developer", "Apple Development"] : ["Developer ID Application"]; return isMas ? ["Mac Developer", "Apple Development"] : ["Developer ID Application"];
} }
return isMas ? ["Apple Distribution"] : ["Developer ID Application"]; return isMas ? ["Apple Distribution"] : ["Developer ID Application"];
} }
//# sourceMappingURL=macPackager.js.map //# sourceMappingURL=macPackager.js.map

View file

@ -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) { exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context; const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== 'darwin') { if (electronPlatformName !== "darwin") {
return; return;
} }
const appName = context.packager.appInfo.productFilename; const appName = context.packager.appInfo.productFilename;
return await notarize({ return await notarize({
appBundleId: 'com.ciderapp.cider', appBundleId: "com.ciderapp.cider",
appPath: `${appOutDir}/${appName}.app`, appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLEID, appleId: process.env.APPLEID,
appleIdPassword: process.env.APPLEIDPASS, appleIdPassword: process.env.APPLEIDPASS,
}); });
}; };

View file

@ -1,26 +1,26 @@
const CiderKit = { const CiderKit = {
v1: { v1: {
musickit: { musickit: {
async mkv3(route, body, options) { async mkv3(route, body, options) {
let opts = { let opts = {
method: 'POST', method: "POST",
cache: 'no-cache', cache: "no-cache",
credentials: 'same-origin', credentials: "same-origin",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
redirect: 'follow', redirect: "follow",
referrerPolicy: 'no-referrer', referrerPolicy: "no-referrer",
body: {} body: {},
} };
opts.body = JSON.stringify({ opts.body = JSON.stringify({
route: route, route: route,
body: body, body: body,
options: options options: options,
}) });
let response = await fetch("./api/musickit/v3", opts); let response = await fetch("./api/musickit/v3", opts);
return response.json() return response.json();
} },
} },
} },
} };

View file

@ -303,10 +303,10 @@
"settings.option.general.resumebehavior.locally.description": "Cider wird die letzte Sitzung auf diesem Rechner fortsetzen.", "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": "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.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": "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.description": "Wähle welcher Tab beim Starten von Cider geöffnet werden soll.",
"settings.option.general.resumetabs.dynamic" : "Dynamisch", "settings.option.general.resumetabs.dynamic": "Dynamisch",
"settings.option.general.resumetabs.dynamic.description" : "Cider wird den zuletzt genutzten Tab öffnen.", "settings.option.general.resumetabs.dynamic.description": "Cider wird den zuletzt genutzten Tab öffnen.",
"settings.option.general.keybindings": "Tastenkombinationen", "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.pressCombination": "Drücke eine Kombination aus zwei Tasten um die Tastenkombination zu aktualisieren.",
"settings.option.general.keybindings.pressEscape": "Drücke Escape um zurückzukehren.", "settings.option.general.keybindings.pressEscape": "Drücke Escape um zurückzukehren.",
@ -396,7 +396,7 @@
"action.selectAll": "Alles auswählen", "action.selectAll": "Alles auswählen",
"action.delete": "Löschen", "action.delete": "Löschen",
"home.syncFavorites": "Sync Favoriten", "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.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.filterTypes": "Medientypen filtern (Last.fm)",
"settings.option.connectivity.lastfmScrobble.manualToken": "Last.fm-Token manuell eingeben" "settings.option.connectivity.lastfmScrobble.manualToken": "Last.fm-Token manuell eingeben"

View file

@ -12,8 +12,8 @@
"action.tray.minimize": "Minimise to Tray", "action.tray.minimize": "Minimise to Tray",
"term.tracks": "songs", "term.tracks": "songs",
"term.track": { "term.track": {
"one" : "song", "one": "song",
"other" : "songs" "other": "songs"
}, },
"home.syncFavorites": "Sync Favourites", "home.syncFavorites": "Sync Favourites",
"home.syncFavorites.gettingArtists": "Getting Favourited Artists...", "home.syncFavorites.gettingArtists": "Getting Favourited Artists...",

View file

@ -642,4 +642,4 @@
"oobe.visual.suggestingThemes.community3": "Dwacuwa", "oobe.visual.suggestingThemes.community3": "Dwacuwa",
"oobe.visual.suggestingThemes.community3.text": "The iconyic Dwacuwa cowow scheme.", "oobe.visual.suggestingThemes.community3.text": "The iconyic Dwacuwa cowow scheme.",
"oobe.amsignin.title": "" "oobe.amsignin.title": ""
} }

View file

@ -202,16 +202,16 @@
"term.confirmLogout": "Are you sure you want to logout?", "term.confirmLogout": "Are you sure you want to logout?",
"term.creditDesignedBy": "Designed by ${authorUsername}", "term.creditDesignedBy": "Designed by ${authorUsername}",
"term.discNumber": "Disc ${discNumber}", "term.discNumber": "Disc ${discNumber}",
"term.reload" : "Reload Cider?", "term.reload": "Reload Cider?",
"term.toggleprivate" : "Toggle Private Session", "term.toggleprivate": "Toggle Private Session",
"term.webremote" : "Web Remote", "term.webremote": "Web Remote",
"term.cast" : "Cast", "term.cast": "Cast",
"term.cast2" : "Cast to Devices", "term.cast2": "Cast to Devices",
"term.quit" : "Quit", "term.quit": "Quit",
"term.zoomin" : "Zoom In", "term.zoomin": "Zoom In",
"term.zoomout" : "Zoom Out", "term.zoomout": "Zoom Out",
"term.zoomreset" : "Reset Zoom", "term.zoomreset": "Reset Zoom",
"term.fullscreen" : "Fullscreen", "term.fullscreen": "Fullscreen",
"term.nowPlaying": "Now Playing", "term.nowPlaying": "Now Playing",
"home.syncFavorites": "Sync Favorites", "home.syncFavorites": "Sync Favorites",
"home.syncFavorites.gettingArtists": "Getting Favorited Artists...", "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.locally.description": "Cider will resume your last session on this machine.",
"settings.option.general.resumebehavior.history": "History", "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.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": "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.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": "Dynamic",
"settings.option.general.resumetabs.dynamic.description" : "Cider will open the tab that you last used.", "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.main": "Languages",
"settings.option.general.language.fun": "Fun Languages", "settings.option.general.language.fun": "Fun Languages",
"settings.option.general.language.unsorted": "Unsorted", "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.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": "Cider Audio Lab",
"settings.option.audio.audioLab.description": "An assortment of in-house developed audio effects for Cider.", "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.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.warn.enableAdvancedFunctionality": "AudioContext (Advanced Functionality) is required to enable this feature.",
"settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Analog Warmth", "settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Analog Warmth",
@ -645,4 +645,4 @@
"oobe.visual.suggestingThemes.community3": "Dracula", "oobe.visual.suggestingThemes.community3": "Dracula",
"oobe.visual.suggestingThemes.community3.text": "The iconic Dracula color scheme.", "oobe.visual.suggestingThemes.community3.text": "The iconic Dracula color scheme.",
"oobe.amsignin.title": "" "oobe.amsignin.title": ""
} }

View file

@ -201,16 +201,16 @@
"term.confirmLogout": "¿Estás seguro de que quieres cerrar sesión?", "term.confirmLogout": "¿Estás seguro de que quieres cerrar sesión?",
"term.creditDesignedBy": "Diseñado por ${authorUsername}", "term.creditDesignedBy": "Diseñado por ${authorUsername}",
"term.discNumber": "Disco ${discNumber}", "term.discNumber": "Disco ${discNumber}",
"term.reload" : "¿ Recargar Cider ?", "term.reload": "¿ Recargar Cider ?",
"term.toggleprivate" : "Cambiar a Sesión Privada", "term.toggleprivate": "Cambiar a Sesión Privada",
"term.webremote" : "Web Remoto", "term.webremote": "Web Remoto",
"term.cast" : "Transmitir", "term.cast": "Transmitir",
"term.cast2" : "Transmitir a los Dispositivos", "term.cast2": "Transmitir a los Dispositivos",
"term.quit" : "Salir", "term.quit": "Salir",
"term.zoomin" : "Acercar", "term.zoomin": "Acercar",
"term.zoomout" : "Alejar", "term.zoomout": "Alejar",
"term.zoomreset" : "Restablecer", "term.zoomreset": "Restablecer",
"term.fullscreen" : "Pantalla Completa", "term.fullscreen": "Pantalla Completa",
"term.nowPlaying": "Reproduciendo Ahora", "term.nowPlaying": "Reproduciendo Ahora",
"home.syncFavorites": "Sincronizar Favoritos", "home.syncFavorites": "Sincronizar Favoritos",
"home.syncFavorites.gettingArtists": "Consiguiendo Artistas Favoritos...", "home.syncFavorites.gettingArtists": "Consiguiendo Artistas Favoritos...",

View file

@ -201,16 +201,16 @@
"term.confirmLogout": "¿Estás seguro de que quieres cerrar sesión?", "term.confirmLogout": "¿Estás seguro de que quieres cerrar sesión?",
"term.creditDesignedBy": "Diseñado por ${authorUsername}", "term.creditDesignedBy": "Diseñado por ${authorUsername}",
"term.discNumber": "Disco ${discNumber}", "term.discNumber": "Disco ${discNumber}",
"term.reload" : "¿ Recargar Cider ?", "term.reload": "¿ Recargar Cider ?",
"term.toggleprivate" : "Cambiar a Sesión Privada", "term.toggleprivate": "Cambiar a Sesión Privada",
"term.webremote" : "Web Remoto", "term.webremote": "Web Remoto",
"term.cast" : "Transmitir", "term.cast": "Transmitir",
"term.cast2" : "Transmitir a los Dispositivos", "term.cast2": "Transmitir a los Dispositivos",
"term.quit" : "Salir", "term.quit": "Salir",
"term.zoomin" : "Acercar", "term.zoomin": "Acercar",
"term.zoomout" : "Alejar", "term.zoomout": "Alejar",
"term.zoomreset" : "Restablecer", "term.zoomreset": "Restablecer",
"term.fullscreen" : "Pantalla Completa", "term.fullscreen": "Pantalla Completa",
"term.nowPlaying": "Reproduciendo Ahora", "term.nowPlaying": "Reproduciendo Ahora",
"home.syncFavorites": "Sincronizar Favoritos", "home.syncFavorites": "Sincronizar Favoritos",
"home.syncFavorites.gettingArtists": "Consiguiendo Artistas Favoritos...", "home.syncFavorites.gettingArtists": "Consiguiendo Artistas Favoritos...",

View file

@ -1,313 +1,312 @@
{ {
"i18n.languageName": "Suomi (FI)", "i18n.languageName": "Suomi (FI)",
"i18n.languageNameEnglish": "Finnish (FI)", "i18n.languageNameEnglish": "Finnish (FI)",
"i18n.category": "main", "i18n.category": "main",
"i18n.authors": "@marcusziade", "i18n.authors": "@marcusziade",
"app.name": "Cider", "app.name": "Cider",
"date.format": "${d} ${m}, ${y}", "date.format": "${d} ${m}, ${y}",
"dialog.cancel": "Peruuta", "dialog.cancel": "Peruuta",
"dialog.ok": "OK", "dialog.ok": "OK",
"notification.updatingLibrarySongs": "Päivitä kirjaston kappaleet...", "notification.updatingLibrarySongs": "Päivitä kirjaston kappaleet...",
"notification.updatingLibraryAlbums": "Päivitä kirjaston albumit...", "notification.updatingLibraryAlbums": "Päivitä kirjaston albumit...",
"notification.updatingLibraryArtists": "Päivitä kirjaston artistit...", "notification.updatingLibraryArtists": "Päivitä kirjaston artistit...",
"term.appleInc": "Apple Inc.", "term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music", "term.appleMusic": "Apple Music",
"term.applePodcasts": "Apple Podcasts", "term.applePodcasts": "Apple Podcasts",
"term.itunes": "iTunes", "term.itunes": "iTunes",
"term.github": "GitHub", "term.github": "GitHub",
"term.discord": "Discord", "term.discord": "Discord",
"term.learnMore": "Näytä lisää", "term.learnMore": "Näytä lisää",
"term.accountSettings": "Tilin asetukset", "term.accountSettings": "Tilin asetukset",
"term.logout": "Kirjaudu ulos", "term.logout": "Kirjaudu ulos",
"term.login": "Kirjaudu sisään", "term.login": "Kirjaudu sisään",
"term.about": "Lisätietoja", "term.about": "Lisätietoja",
"term.privateSession": "Yksityinen tila", "term.privateSession": "Yksityinen tila",
"term.queue": "Jono", "term.queue": "Jono",
"term.search": "Etsi", "term.search": "Etsi",
"term.library": "Kirjasto", "term.library": "Kirjasto",
"term.listenNow": "Kuuntele nyt", "term.listenNow": "Kuuntele nyt",
"term.browse": "Selaa", "term.browse": "Selaa",
"term.radio": "Radio", "term.radio": "Radio",
"term.recentlyAdded": "Viimeksi lisätyt", "term.recentlyAdded": "Viimeksi lisätyt",
"term.songs": "Kappaleet", "term.songs": "Kappaleet",
"term.albums": "Albumit", "term.albums": "Albumit",
"term.artists": "Artistit", "term.artists": "Artistit",
"term.podcasts": "Podcastit", "term.podcasts": "Podcastit",
"term.playlists": "Soittolistat", "term.playlists": "Soittolistat",
"term.playlist": "Soittolista", "term.playlist": "Soittolista",
"term.newPlaylist": "Uusi soittolista", "term.newPlaylist": "Uusi soittolista",
"term.newPlaylistFolder": "Uusi soittolistakansio", "term.newPlaylistFolder": "Uusi soittolistakansio",
"term.createNewPlaylist": "Luo uusi soittolista", "term.createNewPlaylist": "Luo uusi soittolista",
"term.createNewPlaylistFolder": "Luo uusi soittolistakansio", "term.createNewPlaylistFolder": "Luo uusi soittolistakansio",
"term.deletePlaylist": "Oletko varma, että haluat poistaa tämän soittolistan?", "term.deletePlaylist": "Oletko varma, että haluat poistaa tämän soittolistan?",
"term.play": "Soita", "term.play": "Soita",
"term.pause": "Tauko", "term.pause": "Tauko",
"term.previous": "Edellinen", "term.previous": "Edellinen",
"term.next": "Seuraava", "term.next": "Seuraava",
"term.shuffle": "Sekoita", "term.shuffle": "Sekoita",
"term.repeat": "Toista", "term.repeat": "Toista",
"term.volume": "Volyymi", "term.volume": "Volyymi",
"term.mute": "Mykistä", "term.mute": "Mykistä",
"term.unmute": "Poista mykistys", "term.unmute": "Poista mykistys",
"term.share": "Jaa", "term.share": "Jaa",
"term.settings": "Asetukset", "term.settings": "Asetukset",
"term.seeAll": "Näe kaikki", "term.seeAll": "Näe kaikki",
"term.sortBy": "Järjestä", "term.sortBy": "Järjestä",
"term.sortBy.album": "Järjestä albumin mukaan", "term.sortBy.album": "Järjestä albumin mukaan",
"term.sortBy.artist": "Järjestä artistin mukaan", "term.sortBy.artist": "Järjestä artistin mukaan",
"term.sortBy.name": "Järjestä nimen mukaan", "term.sortBy.name": "Järjestä nimen mukaan",
"term.sortBy.genre": "Järjestä genren mukaan", "term.sortBy.genre": "Järjestä genren mukaan",
"term.sortBy.releaseDate": "Julkaisupäivä", "term.sortBy.releaseDate": "Julkaisupäivä",
"term.sortBy.duration": "Pituus", "term.sortBy.duration": "Pituus",
"term.sortOrder": "Järjestys", "term.sortOrder": "Järjestys",
"term.sortOrder.ascending": "Nousevassa järjestyksessä", "term.sortOrder.ascending": "Nousevassa järjestyksessä",
"term.sortOrder.descending": "Laskevassa järjestyksessä", "term.sortOrder.descending": "Laskevassa järjestyksessä",
"term.viewAs": "Näytä kuin", "term.viewAs": "Näytä kuin",
"term.viewAs.coverArt": "Kansikuva", "term.viewAs.coverArt": "Kansikuva",
"term.viewAs.list": "Lista", "term.viewAs.list": "Lista",
"term.size": "Koko", "term.size": "Koko",
"term.size.normal": "Normaali", "term.size.normal": "Normaali",
"term.size.compact": "Kompakti", "term.size.compact": "Kompakti",
"term.enable": "Ota käyttöön", "term.enable": "Ota käyttöön",
"term.disable": "Poista käytöstä", "term.disable": "Poista käytöstä",
"term.enabled": "Käytössä", "term.enabled": "Käytössä",
"term.disabled": "Poissa käytöstä", "term.disabled": "Poissa käytöstä",
"term.connect": "Yhdistä", "term.connect": "Yhdistä",
"term.connecting": "Yhdistää", "term.connecting": "Yhdistää",
"term.disconnect": "Katkaise", "term.disconnect": "Katkaise",
"term.authed": "Tunnistettu", "term.authed": "Tunnistettu",
"term.confirm": "Vahvista", "term.confirm": "Vahvista",
"term.more": "Lisää", "term.more": "Lisää",
"term.less": "Vähemmän", "term.less": "Vähemmän",
"term.showMore": "Näytä lisää", "term.showMore": "Näytä lisää",
"term.showLess": "Näytä vähemmän", "term.showLess": "Näytä vähemmän",
"term.topSongs": "Huippukappaleet", "term.topSongs": "Huippukappaleet",
"term.latestReleases": "Viimeisimmät julkaisut", "term.latestReleases": "Viimeisimmät julkaisut",
"term.time.added": "Lisätty", "term.time.added": "Lisätty",
"term.time.released": "Julkaistu", "term.time.released": "Julkaistu",
"term.time.updated": "Päivitetty", "term.time.updated": "Päivitetty",
"term.time.hours": "Tuntia", "term.time.hours": "Tuntia",
"term.time.hour": "Tunti", "term.time.hour": "Tunti",
"term.time.minutes": "Minuuttiaa", "term.time.minutes": "Minuuttiaa",
"term.time.minute": "Minuutti", "term.time.minute": "Minuutti",
"term.time.seconds": "Sekuntia", "term.time.seconds": "Sekuntia",
"term.time.second": "Sekunti", "term.time.second": "Sekunti",
"term.fullscreenView": "Koko näytön näkymä", "term.fullscreenView": "Koko näytön näkymä",
"term.defaultView": "Oletusnäkymä", "term.defaultView": "Oletusnäkymä",
"term.audioSettings": "Ääniasetukset", "term.audioSettings": "Ääniasetukset",
"term.clearAll": "Puhdista kaikki", "term.clearAll": "Puhdista kaikki",
"term.recentStations": "Viimeisimmät asemat", "term.recentStations": "Viimeisimmät asemat",
"term.language": "Kieli", "term.language": "Kieli",
"term.funLanguages": "Hauskat kielet", "term.funLanguages": "Hauskat kielet",
"term.noLyrics": "Ei sanoituksia", "term.noLyrics": "Ei sanoituksia",
"term.copyright": "Tekijänoikeus", "term.copyright": "Tekijänoikeus",
"term.rightsReserved": "Oikeudet pidätetään", "term.rightsReserved": "Oikeudet pidätetään",
"term.sponsor": "Sponsoroi tätä projektia", "term.sponsor": "Sponsoroi tätä projektia",
"term.ciderTeam": "Cider tiimi", "term.ciderTeam": "Cider tiimi",
"term.developer": "Kehittäjä", "term.developer": "Kehittäjä",
"term.socialTeam": "Sosiaalinen tiimi", "term.socialTeam": "Sosiaalinen tiimi",
"term.socials": "Sosiaaliset mediat", "term.socials": "Sosiaaliset mediat",
"term.contributors": "Avustaja", "term.contributors": "Avustaja",
"term.equalizer": "Taajuuskorjain", "term.equalizer": "Taajuuskorjain",
"term.reset": "Nollaa", "term.reset": "Nollaa",
"term.tracks": "Kappaleita", "term.tracks": "Kappaleita",
"term.videos": "Videoita", "term.videos": "Videoita",
"term.menu": "Valikko", "term.menu": "Valikko",
"term.check": "Tarkista", "term.check": "Tarkista",
"term.aboutArtist": "Lisätiedot {{artistName}}", "term.aboutArtist": "Lisätiedot {{artistName}}",
"home.title": "Koti", "home.title": "Koti",
"home.recentlyPlayed": "Viimeksi soitetut", "home.recentlyPlayed": "Viimeksi soitetut",
"home.recentlyAdded": "Viimeksi lisätyt", "home.recentlyAdded": "Viimeksi lisätyt",
"home.artistsFeed": "Artisti syöte", "home.artistsFeed": "Artisti syöte",
"home.artistsFeed.noArtist": "Seuraa joitain artisteja nähdäksesi heidän uusimmat julkaisunsa.", "home.artistsFeed.noArtist": "Seuraa joitain artisteja nähdäksesi heidän uusimmat julkaisunsa.",
"home.madeForYou": "Tehty sinulle", "home.madeForYou": "Tehty sinulle",
"home.friendsListeningTo": "Kaverit kuuntelee", "home.friendsListeningTo": "Kaverit kuuntelee",
"home.followedArtists": "Seuratut artistit", "home.followedArtists": "Seuratut artistit",
"error.appleMusicSubRequired": "Apple Music vaatii tilauksen.", "error.appleMusicSubRequired": "Apple Music vaatii tilauksen.",
"error.connectionError": "Apple Musiciin yhdistämisessä oli ongelma.", "error.connectionError": "Apple Musiciin yhdistämisessä oli ongelma.",
"error.noResults": "Ei tuloksia.", "error.noResults": "Ei tuloksia.",
"error.noResults.description": "Kokeile uutta hakua.", "error.noResults.description": "Kokeile uutta hakua.",
"podcast.followOnCider": "Seuraa Ciderissa", "podcast.followOnCider": "Seuraa Ciderissa",
"podcast.followedOnCider": "Seurattu Ciderissa", "podcast.followedOnCider": "Seurattu Ciderissa",
"podcast.subscribeOnItunes": "Tilaa iTunesissa", "podcast.subscribeOnItunes": "Tilaa iTunesissa",
"podcast.subscribedOnItunes": "Tilattu iTunesissa", "podcast.subscribedOnItunes": "Tilattu iTunesissa",
"podcast.itunesStore": "iTunes Store", "podcast.itunesStore": "iTunes Store",
"podcast.episodes": "Jakso", "podcast.episodes": "Jakso",
"podcast.playEpisode": "Toista jakso", "podcast.playEpisode": "Toista jakso",
"podcast.website": "Avaa nettisivu", "podcast.website": "Avaa nettisivu",
"action.addToLibrary": "Lisää kirjastoon", "action.addToLibrary": "Lisää kirjastoon",
"action.addToLibrary.success": "Lisätty kirjastoon", "action.addToLibrary.success": "Lisätty kirjastoon",
"action.addToLibrary.error": "Virhe lisättäessä kirjastoon", "action.addToLibrary.error": "Virhe lisättäessä kirjastoon",
"action.removeFromLibrary": "Poista kirjastosta", "action.removeFromLibrary": "Poista kirjastosta",
"action.removeFromLibrary.success": "Poistettu kirjastosta", "action.removeFromLibrary.success": "Poistettu kirjastosta",
"action.addToQueue": "Lisää jonoon", "action.addToQueue": "Lisää jonoon",
"action.addToQueue.success": "Lisätty jonoon", "action.addToQueue.success": "Lisätty jonoon",
"action.addToQueue.error": "Virhe lisättäessä jonoon", "action.addToQueue.error": "Virhe lisättäessä jonoon",
"action.removeFromQueue": "Poista jonosta", "action.removeFromQueue": "Poista jonosta",
"action.removeFromQueue.success": "Poistettu jonosta", "action.removeFromQueue.success": "Poistettu jonosta",
"action.removeFromQueue.error": "Virhe poistettaessa jonosta", "action.removeFromQueue.error": "Virhe poistettaessa jonosta",
"action.createPlaylist": "Luo uusi soittolista", "action.createPlaylist": "Luo uusi soittolista",
"action.addToPlaylist": "Lisää soittolistaan", "action.addToPlaylist": "Lisää soittolistaan",
"action.removeFromPlaylist": "Poista soittolistasta", "action.removeFromPlaylist": "Poista soittolistasta",
"action.addToFavorites": "Lisää suosikkeihin", "action.addToFavorites": "Lisää suosikkeihin",
"action.follow": "Seuraa", "action.follow": "Seuraa",
"action.follow.success": "Seurattu", "action.follow.success": "Seurattu",
"action.follow.error": "Virhe seurattaessa", "action.follow.error": "Virhe seurattaessa",
"action.unfollow": "Lopeta seuraaminen", "action.unfollow": "Lopeta seuraaminen",
"action.unfollow.success": "Seuraaminen lopetettu", "action.unfollow.success": "Seuraaminen lopetettu",
"action.unfollow.error": "Virhe seuraamisen lopetuksessa", "action.unfollow.error": "Virhe seuraamisen lopetuksessa",
"action.playNext": "Toista seuraavaksi", "action.playNext": "Toista seuraavaksi",
"action.playLater": "Toista myöhemmin", "action.playLater": "Toista myöhemmin",
"action.startRadio": "Aloita radio", "action.startRadio": "Aloita radio",
"action.goToArtist": "Näytä artisti", "action.goToArtist": "Näytä artisti",
"action.goToAlbum": "Näytä albumi", "action.goToAlbum": "Näytä albumi",
"action.moveToTop": "Siirrä kärkeen", "action.moveToTop": "Siirrä kärkeen",
"action.share": "Jaa", "action.share": "Jaa",
"action.rename": "Nimeä uudelleen", "action.rename": "Nimeä uudelleen",
"action.love": "Tykkää", "action.love": "Tykkää",
"action.unlove": "Poista tykkäys", "action.unlove": "Poista tykkäys",
"action.dislike": "En tykkää", "action.dislike": "En tykkää",
"action.undoDislike": "Kumoa en tykkää", "action.undoDislike": "Kumoa en tykkää",
"action.showWebRemoteQR": "Cider web kaukoohjain", "action.showWebRemoteQR": "Cider web kaukoohjain",
"action.playTracksNext": "Toista ${app.selectedMediaItems.length} kappaleet seuraavaksi", "action.playTracksNext": "Toista ${app.selectedMediaItems.length} kappaleet seuraavaksi",
"action.playTracksLater": "Toista ${app.selectedMediaItems.length} kappaleet myöhemmin", "action.playTracksLater": "Toista ${app.selectedMediaItems.length} kappaleet myöhemmin",
"action.removeTracks": "Poista ${self.selectedItems.length} kappaleet jonosta", "action.removeTracks": "Poista ${self.selectedItems.length} kappaleet jonosta",
"action.import": "Tuonti", "action.import": "Tuonti",
"action.export": "Vienti", "action.export": "Vienti",
"action.showAlbum": "Näytä albumi", "action.showAlbum": "Näytä albumi",
"action.tray.minimize": "Pienennä", "action.tray.minimize": "Pienennä",
"action.tray.quit": "Sammuta", "action.tray.quit": "Sammuta",
"action.tray.show": "Näytä", "action.tray.show": "Näytä",
"action.update": "Päivitä", "action.update": "Päivitä",
"settings.header.general": "Yleistä", "settings.header.general": "Yleistä",
"settings.header.general.description": "Muuta yleisasetuksia", "settings.header.general.description": "Muuta yleisasetuksia",
"settings.option.general.language": "Kieli", "settings.option.general.language": "Kieli",
"settings.option.general.language.main": "Kieli", "settings.option.general.language.main": "Kieli",
"settings.option.general.language.fun": "Hauskat kielet", "settings.option.general.language.fun": "Hauskat kielet",
"settings.option.general.language.unsorted": "Lajittelematon", "settings.option.general.language.unsorted": "Lajittelematon",
"settings.header.audio": "Ääni", "settings.header.audio": "Ääni",
"settings.header.audio.description": "Muuta ääniasetuksia", "settings.header.audio.description": "Muuta ääniasetuksia",
"settings.option.audio.quality": "Äänenlaatu", "settings.option.audio.quality": "Äänenlaatu",
"settings.header.audio.quality.high": "Korkea", "settings.header.audio.quality.high": "Korkea",
"settings.option.audio.seamlessTransition": "Saumaton siirtyminen", "settings.option.audio.seamlessTransition": "Saumaton siirtyminen",
"settings.option.audio.enableAdvancedFunctionality": "Ota lisätoiminnot käyttöön", "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.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.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": "Ää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.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": "Ulkonäkö",
"settings.header.visual.description": "Muuta ulkonäköä.", "settings.header.visual.description": "Muuta ulkonäköä.",
"settings.option.visual.windowBackgroundStyle": "Taustatyyli", "settings.option.visual.windowBackgroundStyle": "Taustatyyli",
"settings.header.visual.windowBackgroundStyle.none": "Ei taustatyyliä", "settings.header.visual.windowBackgroundStyle.none": "Ei taustatyyliä",
"settings.header.visual.windowBackgroundStyle.artwork": "Taideteos", "settings.header.visual.windowBackgroundStyle.artwork": "Taideteos",
"settings.header.visual.windowBackgroundStyle.image": "Kuva", "settings.header.visual.windowBackgroundStyle.image": "Kuva",
"settings.option.visual.animatedArtwork": "Animoitu taideteos", "settings.option.visual.animatedArtwork": "Animoitu taideteos",
"settings.header.visual.animatedArtwork.always": "Aina", "settings.header.visual.animatedArtwork.always": "Aina",
"settings.header.visual.animatedArtwork.limited": "Ainoastaa sivuilla joilla se on tarvittua", "settings.header.visual.animatedArtwork.limited": "Ainoastaa sivuilla joilla se on tarvittua",
"settings.header.visual.animatedArtwork.disable": "Ei koskaan", "settings.header.visual.animatedArtwork.disable": "Ei koskaan",
"settings.option.visual.animatedArtworkQuality": "Animoinnin laatu", "settings.option.visual.animatedArtworkQuality": "Animoinnin laatu",
"settings.header.visual.animatedArtworkQuality.low": "Alhainen", "settings.header.visual.animatedArtworkQuality.low": "Alhainen",
"settings.header.visual.animatedArtworkQuality.medium": "Keskinkertainen", "settings.header.visual.animatedArtworkQuality.medium": "Keskinkertainen",
"settings.header.visual.animatedArtworkQuality.high": "Korkea", "settings.header.visual.animatedArtworkQuality.high": "Korkea",
"settings.header.visual.animatedArtworkQuality.veryHigh": "Erittäin korkea", "settings.header.visual.animatedArtworkQuality.veryHigh": "Erittäin korkea",
"settings.header.visual.animatedArtworkQuality.extreme": "Korkein", "settings.header.visual.animatedArtworkQuality.extreme": "Korkein",
"settings.option.visual.animatedWindowBackground": "Animoitu tausta", "settings.option.visual.animatedWindowBackground": "Animoitu tausta",
"settings.option.visual.hardwareAcceleration": "Laitteistokiihdytys", "settings.option.visual.hardwareAcceleration": "Laitteistokiihdytys",
"settings.option.visual.hardwareAcceleration.description": "Vaatii uudelleenkäynnistyksen", "settings.option.visual.hardwareAcceleration.description": "Vaatii uudelleenkäynnistyksen",
"settings.header.visual.hardwareAcceleration.default": "Vakio", "settings.header.visual.hardwareAcceleration.default": "Vakio",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU", "settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
"settings.option.visual.showPersonalInfo": "Näytä henkilökohtaiset tiedot", "settings.option.visual.showPersonalInfo": "Näytä henkilökohtaiset tiedot",
"settings.header.lyrics": "Sanoitukset", "settings.header.lyrics": "Sanoitukset",
"settings.header.lyrics.description": "Muuta sanoitusasetuksia", "settings.header.lyrics.description": "Muuta sanoitusasetuksia",
"settings.option.lyrics.enableMusixmatch": "Käytä MusicXMatchia Apple Music sanoituksien sijaan", "settings.option.lyrics.enableMusixmatch": "Käytä MusicXMatchia Apple Music sanoituksien sijaan",
"settings.option.lyrics.enableMusixmatchKaraoke": "Aktivoi karaoketila (Vain MusicXMatch)", "settings.option.lyrics.enableMusixmatchKaraoke": "Aktivoi karaoketila (Vain MusicXMatch)",
"settings.option.lyrics.musixmatchPreferredLanguage": "MusicXMatch ensisijainen kieli", "settings.option.lyrics.musixmatchPreferredLanguage": "MusicXMatch ensisijainen kieli",
"settings.option.lyrics.enableYoutubeLyrics": "Käytä Youtube sanoituksia videoissa", "settings.option.lyrics.enableYoutubeLyrics": "Käytä Youtube sanoituksia videoissa",
"settings.header.connectivity": "Yhteys", "settings.header.connectivity": "Yhteys",
"settings.header.connectivity.description": "Muuta yhteysasetuksia", "settings.header.connectivity.description": "Muuta yhteysasetuksia",
"settings.option.connectivity.discordRPC": "Discord integraatio (discordRPC)", "settings.option.connectivity.discordRPC": "Discord integraatio (discordRPC)",
"settings.option.connectivity.playbackNotifications": "Toistoilmoitukset", "settings.option.connectivity.playbackNotifications": "Toistoilmoitukset",
"settings.option.connectivity.discordRPC.clearOnPause": "Poista Discord ilmoitus, kun tauotat kappaleen", "settings.option.connectivity.discordRPC.clearOnPause": "Poista Discord ilmoitus, kun tauotat kappaleen",
"settings.option.connectivity.lastfmScrobble": "Last.fm integraatio", "settings.option.connectivity.lastfmScrobble": "Last.fm integraatio",
"settings.option.connectivity.lastfmScrobble.delay": "Last.fm viive i %", "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.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.removeFeatured": "Älä näytä extra tietoja Last.fm palvelussa",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Suodata pakkotoisteut kappaleet", "settings.option.connectivity.lastfmScrobble.filterLoop": "Suodata pakkotoisteut kappaleet",
"settings.header.experimental": "Testi", "settings.header.experimental": "Testi",
"settings.header.experimental.description": "Muuta testi asetuksia.", "settings.header.experimental.description": "Muuta testi asetuksia.",
"settings.option.experimental.compactUI": "Kompakti näkymä", "settings.option.experimental.compactUI": "Kompakti näkymä",
"settings.option.window.close_button_hide": "Suljenappi pienentää Cider ikkunan", "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.notTurnedOn": "Äänen spatialisointi on poistettu käytöstä. Ota se käyttöön ennen käyttöä.",
"spatial.spatialProperties": "Äänen spatialisointi asetukset", "spatial.spatialProperties": "Äänen spatialisointi asetukset",
"spatial.width": "Leveys", "spatial.width": "Leveys",
"spatial.height": "Korkeus", "spatial.height": "Korkeus",
"spatial.depth": "Syvyys", "spatial.depth": "Syvyys",
"spatial.gain": "Tilavyöhyke", "spatial.gain": "Tilavyöhyke",
"spatial.roomMaterials": "Huoneen materiaalit", "spatial.roomMaterials": "Huoneen materiaalit",
"spatial.roomDimensions": "Huoneen koko", "spatial.roomDimensions": "Huoneen koko",
"spatial.roomPositions": "Huoneen sijainti", "spatial.roomPositions": "Huoneen sijainti",
"spatial.setDimensions": "Valitse koko", "spatial.setDimensions": "Valitse koko",
"spatial.setPositions": "Valitse sijainnit", "spatial.setPositions": "Valitse sijainnit",
"spatial.up": "Ylös", "spatial.up": "Ylös",
"spatial.front": "Eteenpäin", "spatial.front": "Eteenpäin",
"spatial.left": "Vasemmalle", "spatial.left": "Vasemmalle",
"spatial.right": "Oikealle", "spatial.right": "Oikealle",
"spatial.back": "Taaksepäin", "spatial.back": "Taaksepäin",
"spatial.down": "Alaspäin", "spatial.down": "Alaspäin",
"spatial.listener": "Kuuntelija", "spatial.listener": "Kuuntelija",
"spatial.audioSource": "Äänenlähde", "spatial.audioSource": "Äänenlähde",
"settings.header.unfinished": "Keskeneräinen", "settings.header.unfinished": "Keskeneräinen",
"remote.web.title": "Ciderin kaukosäädin", "remote.web.title": "Ciderin kaukosäädin",
"remote.web.description": "Skannaa QR-koodi yhdistääksesi puhelimesi tämän Cider-instanssin kanssa", "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.", "about.thanks": "Suuri kiitos Cider Collective Teamille ja kaikille avustajillemme.",
"oobe.yes": "Kyllä", "oobe.yes": "Kyllä",
"oobe.no": "Ei", "oobe.no": "Ei",
"oobe.next": "Seuraava", "oobe.next": "Seuraava",
"oobe.previous": "Edellinen", "oobe.previous": "Edellinen",
"oobe.done": "Valmis", "oobe.done": "Valmis",
"oobe.amupsell.title": "Ennenkuin aloitamme", "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.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.subscribeBtn": "Tilaa Apple Music",
"oobe.amupsell.explainBtn": "Selitä", "oobe.amupsell.explainBtn": "Selitä",
"oobe.amupsell.subscribeUrl": "https://apple.co/3MdqJVQ", "oobe.amupsell.subscribeUrl": "https://apple.co/3MdqJVQ",
"oobe.amupsell.amWebUrl": "https://beta.music.apple.com/", "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.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.title": "Tervetuloa Cideriin",
"oobe.intro.subtitle": "", "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.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.title": "Yleistä",
"oobe.general.subtitle": "", "oobe.general.subtitle": "",
"oobe.general.text": "", "oobe.general.text": "",
"oobe.audio.title": "Ääni", "oobe.audio.title": "Ääni",
"oobe.audio.subtitle": "", "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.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.audio.advancedFunctionality": "",
"oobe.visual.title": "Ulkonäkö", "oobe.visual.title": "Ulkonäkö",
"oobe.visual.subtitle": "", "oobe.visual.subtitle": "",
"oobe.visual.text": "", "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.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": "Teema on loistava tapa muokata kokemustasi. Tässä on muutamia ehdotuksia: ",
"oobe.visual.suggestingThemes.subtext": "(Nämä teemat ladataan GitHubista)", "oobe.visual.suggestingThemes.subtext": "(Nämä teemat ladataan GitHubista)",
"oobe.visual.suggestingThemes.default": "Cider", "oobe.visual.suggestingThemes.default": "Cider",
"oobe.visual.suggestingThemes.default.text": "Klassinen Ciderteema.", "oobe.visual.suggestingThemes.default.text": "Klassinen Ciderteema.",
"oobe.visual.suggestingThemes.dark": "Pimeys", "oobe.visual.suggestingThemes.dark": "Pimeys",
"oobe.visual.suggestingThemes.dark.text": "Pimeys", "oobe.visual.suggestingThemes.dark.text": "Pimeys",
"oobe.visual.suggestingThemes.community1": "Groovy", "oobe.visual.suggestingThemes.community1": "Groovy",
"oobe.visual.suggestingThemes.community1.text": "WinUI-vaikutteinen teema", "oobe.visual.suggestingThemes.community1.text": "WinUI-vaikutteinen teema",
"oobe.visual.suggestingThemes.community2": "iTheme", "oobe.visual.suggestingThemes.community2": "iTheme",
"oobe.visual.suggestingThemes.community2.text": "Klassinen Big fruit teema", "oobe.visual.suggestingThemes.community2.text": "Klassinen Big fruit teema",
"oobe.visual.suggestingThemes.community3": "Dracula", "oobe.visual.suggestingThemes.community3": "Dracula",
"oobe.visual.suggestingThemes.community3.text": "Ikoninen Dracula-värimaailma", "oobe.visual.suggestingThemes.community3.text": "Ikoninen Dracula-värimaailma",
"oobe.amsignin.title": "", "oobe.amsignin.title": "",
"share.platform.twitter.tweet": "Kuuntele kappaletta {{song}} Apple Musicissa.\n\n{{url}}\n\n#AppleMusic #Cider", "share.platform.twitter.tweet": "Kuuntele kappaletta {{song}} Apple Musicissa.\n\n{{url}}\n\n#AppleMusic #Cider",
"share.platform.twitter": "Twitter", "share.platform.twitter": "Twitter",
"share.platform.facebook": "Facebook", "share.platform.facebook": "Facebook",
"share.platform.reddit": "Reddit", "share.platform.reddit": "Reddit",
"share.platform.telegram": "Telegram", "share.platform.telegram": "Telegram",
"share.platform.whatsapp": "WhatsApp", "share.platform.whatsapp": "WhatsApp",
"share.platform.messenger": "Messenger", "share.platform.messenger": "Messenger",
"share.platform.email": "Sähköposti", "share.platform.email": "Sähköposti",
"share.platform.songLink": "Kopioi song.link", "share.platform.songLink": "Kopioi song.link",
"share.platform.clipboard": "Kopioi linkki" "share.platform.clipboard": "Kopioi linkki"
} }

View file

@ -195,16 +195,16 @@
"term.confirmLogout": "Apakah Anda yakin ingin keluar??", "term.confirmLogout": "Apakah Anda yakin ingin keluar??",
"term.creditDesignedBy": "Dirancang oleh ${authorUsername}", "term.creditDesignedBy": "Dirancang oleh ${authorUsername}",
"term.discNumber": "Kaset ${discNumber}", "term.discNumber": "Kaset ${discNumber}",
"term.reload" : "Muat ulang Cider?", "term.reload": "Muat ulang Cider?",
"term.toggleprivate" : "Nyalakan Sesi Pribadi", "term.toggleprivate": "Nyalakan Sesi Pribadi",
"term.webremote" : "Remot Web", "term.webremote": "Remot Web",
"term.cast" : "Transmisi", "term.cast": "Transmisi",
"term.cast2" : "Transmisikan ke Perangkat", "term.cast2": "Transmisikan ke Perangkat",
"term.quit" : "Keluar", "term.quit": "Keluar",
"term.zoomin" : "Perbesar", "term.zoomin": "Perbesar",
"term.zoomout" : "Perkecil", "term.zoomout": "Perkecil",
"term.zoomreset" : "Atur Ulang", "term.zoomreset": "Atur Ulang",
"term.fullscreen" : "Layar Penuh", "term.fullscreen": "Layar Penuh",
"home.syncFavorites": "Sinkronkan Favorit", "home.syncFavorites": "Sinkronkan Favorit",
"home.syncFavorites.gettingArtists": "Mendapatkan artis favorit", "home.syncFavorites.gettingArtists": "Mendapatkan artis favorit",
"home.title": "Beranda", "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.locally.description": "Cider akan melanjutkan sesi terakhir Anda di perangkat ini.",
"settings.option.general.resumebehavior.history": "Riwayat", "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.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": "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.description": "Anda dapat memilih tab apa yang akan dibuka ketika Anda membuka Cider.",
"settings.option.general.resumetabs.dynamic" : "Dinamis", "settings.option.general.resumetabs.dynamic": "Dinamis",
"settings.option.general.resumetabs.dynamic.description" : "Cider akan membuka tab yang terakhir digunakan", "settings.option.general.resumetabs.dynamic.description": "Cider akan membuka tab yang terakhir digunakan",
"settings.option.general.language.main": "Bahasa", "settings.option.general.language.main": "Bahasa",
"settings.option.general.language.fun": "Bahasa Candaan", "settings.option.general.language.fun": "Bahasa Candaan",
"settings.option.general.language.unsorted": "Tidak disortir", "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.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": "Lab Audio Cider",
"settings.option.audio.audioLab.description": "Macam-macam efek audio yang dikembangkan sendiri untuk 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.audioLab.withoutAF": "AudioContext (Fungsi Lanjutan) perlu diaktifkan untuk menggunakan Lab Audio Cider.",
"settings.warn.enableAdvancedFunctionality": "AudioContext (Advanced Functionality) dibutuhkan untuk menyalakan fitur ini.", "settings.warn.enableAdvancedFunctionality": "AudioContext (Advanced Functionality) dibutuhkan untuk menyalakan fitur ini.",
"settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Analog Warmth", "settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Analog Warmth",

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,7 @@
"notification.updatingLibrarySongs": "Обновление медиатеки песен...", "notification.updatingLibrarySongs": "Обновление медиатеки песен...",
"notification.updatingLibraryAlbums": "Обновление медиатеки альбомов...", "notification.updatingLibraryAlbums": "Обновление медиатеки альбомов...",
"notification.updatingLibraryArtists": "Обновление медиатеки артистов...", "notification.updatingLibraryArtists": "Обновление медиатеки артистов...",
"term.variables": "Переменные", "term.variables": "Переменные",
"term.appleInc": "Apple Inc.", "term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music", "term.appleMusic": "Apple Music",
"term.applePodcasts": "Подкасты Apple", "term.applePodcasts": "Подкасты Apple",
@ -26,7 +26,7 @@
"term.privateSession": "Частная сессия", "term.privateSession": "Частная сессия",
"term.disablePrivateSession": "Выключить частную сессию", "term.disablePrivateSession": "Выключить частную сессию",
"term.queue": "Очередь", "term.queue": "Очередь",
"term.autoplay": "Автовоспроизведение", "term.autoplay": "Автовоспроизведение",
"term.lyrics": "Текст", "term.lyrics": "Текст",
"term.miniplayer": "Мини-проигрыватель", "term.miniplayer": "Мини-проигрыватель",
"term.history": "История", "term.history": "История",
@ -209,16 +209,16 @@
"term.confirmLogout": "Вы уверены, что хотите выйти?", "term.confirmLogout": "Вы уверены, что хотите выйти?",
"term.creditDesignedBy": "Разработано ${authorUsername}", "term.creditDesignedBy": "Разработано ${authorUsername}",
"term.discNumber": "Диск ${discNumber}", "term.discNumber": "Диск ${discNumber}",
"term.reload" : "Перезагрузить Cider?", "term.reload": "Перезагрузить Cider?",
"term.toggleprivate" : "Переключить частную сессию", "term.toggleprivate": "Переключить частную сессию",
"term.webremote" : "Web Remote", "term.webremote": "Web Remote",
"term.cast" : "Транслировать", "term.cast": "Транслировать",
"term.cast2" : "Трансляция на устройства", "term.cast2": "Трансляция на устройства",
"term.quit" : "Выход", "term.quit": "Выход",
"term.zoomin" : "Приблизить", "term.zoomin": "Приблизить",
"term.zoomout" : "Отдалить", "term.zoomout": "Отдалить",
"term.zoomreset" : "Сбросить масштаб", "term.zoomreset": "Сбросить масштаб",
"term.fullscreen" : "Полный экран", "term.fullscreen": "Полный экран",
"term.nowPlaying": "Сейчас играет", "term.nowPlaying": "Сейчас играет",
"home.syncFavorites": "Синхронизировать", "home.syncFavorites": "Синхронизировать",
"home.syncFavorites.gettingArtists": "Получение отслеживаемых исполнителей...", "home.syncFavorites.gettingArtists": "Получение отслеживаемых исполнителей...",
@ -347,10 +347,10 @@
"settings.option.general.resumebehavior.locally.description": "Cider возобновит ваш последний сеанс на этом компьютере.", "settings.option.general.resumebehavior.locally.description": "Cider возобновит ваш последний сеанс на этом компьютере.",
"settings.option.general.resumebehavior.history": "История", "settings.option.general.resumebehavior.history": "История",
"settings.option.general.resumebehavior.history.description": "Cider поставит в очередь последнюю песню из вашей общей истории Apple Music на разных устройствах.", "settings.option.general.resumebehavior.history.description": "Cider поставит в очередь последнюю песню из вашей общей истории Apple Music на разных устройствах.",
"settings.option.general.resumetabs" : "Раздел при запуске", "settings.option.general.resumetabs": "Раздел при запуске",
"settings.option.general.resumetabs.description" : "Вы можете выбрать, какой раздел будет открыться при запуске Cider.", "settings.option.general.resumetabs.description": "Вы можете выбрать, какой раздел будет открыться при запуске Cider.",
"settings.option.general.resumetabs.dynamic" : "Динамически", "settings.option.general.resumetabs.dynamic": "Динамически",
"settings.option.general.resumetabs.dynamic.description" : "Cider откроет последний использованный раздел.", "settings.option.general.resumetabs.dynamic.description": "Cider откроет последний использованный раздел.",
"settings.option.general.language.main": "Языки", "settings.option.general.language.main": "Языки",
"settings.option.general.language.fun": "Забавные языки", "settings.option.general.language.fun": "Забавные языки",
"settings.option.general.language.unsorted": "Неотсортированные", "settings.option.general.language.unsorted": "Неотсортированные",
@ -408,9 +408,9 @@
"settings.warn.audio.enableAdvancedFunctionality.lowcores": "Cider считает, что ваш компьютер не справится с этими функциями. Вы уверены что хотите продолжить?", "settings.warn.audio.enableAdvancedFunctionality.lowcores": "Cider считает, что ваш компьютер не справится с этими функциями. Вы уверены что хотите продолжить?",
"settings.option.audio.audioLab": "Cider Audio Lab", "settings.option.audio.audioLab": "Cider Audio Lab",
"settings.option.audio.audioLab.description": "Ассортимент звуковых обработок собственной разработки для Cider.", "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.audioLab.withoutAF": "AudioContext (Расширенный функционал) требуется для включения Cider Audio Laboratory.",
"settings.warn.enableAdvancedFunctionality": "Для включения этой функции требуется AudioContext (расширенный функционал).", "settings.warn.enableAdvancedFunctionality": "Для включения этой функции требуется AudioContext (расширенный функционал).",
"settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Аналоговое звучание", "settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Аналоговое звучание",
"settings.option.audio.enableAdvancedFunctionality.analogWarmth.description": "Имитирует аналоговое звучание по образцу Korg Nutube 6P1", "settings.option.audio.enableAdvancedFunctionality.analogWarmth.description": "Имитирует аналоговое звучание по образцу Korg Nutube 6P1",
"settings.option.audio.enableAdvancedFunctionality.analogWarmthIntensity": "Интенсивность аналогового звучания", "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.BSCBM": "Brown Sugar Creme Brûlée Milk",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500B": "Clafoutis aux Cerises", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500B": "Clafoutis aux Cerises",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500C": "Uji Matcha Mochi", "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": "Cider Adrenaline Processor™",
"settings.option.audio.enableAdvancedFunctionality.ciderPPE.description": "Улучшает воспринимаемое качество звука AAC 256 кбит/с за счет использования алгоритма реального времени, использующего как психоакустические модели человеческого слуха, так и характеристики кодирования AAC.", "settings.option.audio.enableAdvancedFunctionality.ciderPPE.description": "Улучшает воспринимаемое качество звука AAC 256 кбит/с за счет использования алгоритма реального времени, использующего как психоакустические модели человеческого слуха, так и характеристики кодирования AAC.",
"settings.warn.audio.enableAdvancedFunctionality.ciderPPE.compatibility": "CAP не совместим с пространственным звучанием. Пожалуйста, отключите пространственное звучание, чтобы продолжить.", "settings.warn.audio.enableAdvancedFunctionality.ciderPPE.compatibility": "CAP не совместим с пространственным звучанием. Пожалуйста, отключите пространственное звучание, чтобы продолжить.",
@ -440,7 +440,7 @@
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.standard": "Стандартный", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.standard": "Стандартный",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.adaptive": "Адаптивный", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.adaptive": "Адаптивный",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.legacy": "Legacy", "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": "Нормализация звука",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Нормализует пиковую громкость для отдельных треков, чтобы создать более однородное впечатление от прослушивания.", "settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Нормализует пиковую громкость для отдельных треков, чтобы создать более однородное впечатление от прослушивания.",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.disabled": "Управляется Audio Lab", "settings.option.audio.enableAdvancedFunctionality.audioNormalization.disabled": "Управляется Audio Lab",

View file

@ -202,16 +202,16 @@
"term.confirmLogout": "Are you sure you want to logout?", "term.confirmLogout": "Are you sure you want to logout?",
"term.creditDesignedBy": "Designed by ${authorUsername}", "term.creditDesignedBy": "Designed by ${authorUsername}",
"term.discNumber": "Disc ${discNumber}", "term.discNumber": "Disc ${discNumber}",
"term.reload" : "Reload Cider?", "term.reload": "Reload Cider?",
"term.toggleprivate" : "Toggle Private Session", "term.toggleprivate": "Toggle Private Session",
"term.webremote" : "Web Remote", "term.webremote": "Web Remote",
"term.cast" : "Cast", "term.cast": "Cast",
"term.cast2" : "Cast to Devices", "term.cast2": "Cast to Devices",
"term.quit" : "Quit", "term.quit": "Quit",
"term.zoomin" : "Zoom In", "term.zoomin": "Zoom In",
"term.zoomout" : "Zoom Out", "term.zoomout": "Zoom Out",
"term.zoomreset" : "Reset Zoom", "term.zoomreset": "Reset Zoom",
"term.fullscreen" : "Fullscreen", "term.fullscreen": "Fullscreen",
"term.nowPlaying": "Now Playing", "term.nowPlaying": "Now Playing",
"home.syncFavorites": "Sync Favorites", "home.syncFavorites": "Sync Favorites",
"home.syncFavorites.gettingArtists": "Getting Favorited Artists...", "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.locally.description": "Cider will resume your last session on this machine.",
"settings.option.general.resumebehavior.history": "History", "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.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": "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.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": "Dynamic",
"settings.option.general.resumetabs.dynamic.description" : "Cider will open the tab that you last used.", "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.main": "Languages",
"settings.option.general.language.fun": "Fun Languages", "settings.option.general.language.fun": "Fun Languages",
"settings.option.general.language.unsorted": "Unsorted", "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.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": "Cider Audio Lab",
"settings.option.audio.audioLab.description": "An assortment of in-house developed audio effects for Cider.", "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.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.warn.enableAdvancedFunctionality": "AudioContext (Advanced Functionality) is required to enable this feature.",
"settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Analog Warmth", "settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Analog Warmth",
@ -643,4 +643,4 @@
"oobe.visual.suggestingThemes.community3": "Dracula", "oobe.visual.suggestingThemes.community3": "Dracula",
"oobe.visual.suggestingThemes.community3.text": "The iconic Dracula color scheme.", "oobe.visual.suggestingThemes.community3.text": "The iconic Dracula color scheme.",
"oobe.amsignin.title": "" "oobe.amsignin.title": ""
} }

View file

@ -186,16 +186,16 @@
"term.confirmLogout": "你确定要退出登录吗?", "term.confirmLogout": "你确定要退出登录吗?",
"term.creditDesignedBy": "由 ${authorUsername} 设计", "term.creditDesignedBy": "由 ${authorUsername} 设计",
"term.discNumber": "碟 ${discNumber}", "term.discNumber": "碟 ${discNumber}",
"term.reload" : "重新载入 Cider?", "term.reload": "重新载入 Cider?",
"term.toggleprivate": "切换隐身聆听", "term.toggleprivate": "切换隐身聆听",
"term.webremote": "远程控制", "term.webremote": "远程控制",
"term.cast": "投射", "term.cast": "投射",
"term.cast2" : "投射到设备", "term.cast2": "投射到设备",
"term.quit" : "退出应用", "term.quit": "退出应用",
"term.zoomin" : "放大", "term.zoomin": "放大",
"term.zoomout" : "缩小", "term.zoomout": "缩小",
"term.zoomreset" : "重置缩放", "term.zoomreset": "重置缩放",
"term.fullscreen" : "全屏模式", "term.fullscreen": "全屏模式",
"term.nowPlaying": "正在播放", "term.nowPlaying": "正在播放",
"home.syncFavorites": "同步喜爱艺人", "home.syncFavorites": "同步喜爱艺人",
"home.syncFavorites.gettingArtists": "获取喜爱艺人...", "home.syncFavorites.gettingArtists": "获取喜爱艺人...",

View file

@ -19,10 +19,10 @@
"term.accountSettings": "帳戶設定", "term.accountSettings": "帳戶設定",
"term.logout": "登出", "term.logout": "登出",
"term.login": "登入", "term.login": "登入",
"term.quit" : "結束", "term.quit": "結束",
"term.about": "關於", "term.about": "關於",
"term.cast" : "投影", "term.cast": "投影",
"term.cast2" : "投影到裝置", "term.cast2": "投影到裝置",
"term.privateSession": "私人時間", "term.privateSession": "私人時間",
"term.queue": "待播清單", "term.queue": "待播清單",
"term.lyrics": "歌詞", "term.lyrics": "歌詞",
@ -146,14 +146,14 @@
"term.plugins": "模組", "term.plugins": "模組",
"term.pluginMenu": "模組選單", "term.pluginMenu": "模組選單",
"term.pluginMenu.none": "沒有交互式模組", "term.pluginMenu.none": "沒有交互式模組",
"term.fullscreen" : "全螢幕模式", "term.fullscreen": "全螢幕模式",
"home.title": "首頁", "home.title": "首頁",
"home.recentlyPlayed": "最近播放", "home.recentlyPlayed": "最近播放",
"home.recentlyAdded": "最近加入", "home.recentlyAdded": "最近加入",
"home.artistsFeed": "藝人追蹤", "home.artistsFeed": "藝人追蹤",
"home.artistsFeed.noArtist": "追蹤你喜愛的藝人來取得他們的最新發行歌曲。", "home.artistsFeed.noArtist": "追蹤你喜愛的藝人來取得他們的最新發行歌曲。",
"home.syncFavorites" : "同步追蹤" , "home.syncFavorites": "同步追蹤",
"home.syncFavorites.gettingArtists" : "取得追蹤的藝人歌手列表... " , "home.syncFavorites.gettingArtists": "取得追蹤的藝人歌手列表... ",
"home.madeForYou": "為您推薦", "home.madeForYou": "為您推薦",
"home.friendsListeningTo": "朋友正在聆聽", "home.friendsListeningTo": "朋友正在聆聽",
"home.followedArtists": "追蹤的藝人", "home.followedArtists": "追蹤的藝人",

View file

@ -1,281 +1,281 @@
{ {
"i18n.languageName": "廣東話(香港)", "i18n.languageName": "廣東話(香港)",
"i18n.languageNameEnglish": "Cantonese (Hong Kong)", "i18n.languageNameEnglish": "Cantonese (Hong Kong)",
"i18n.category": "main", "i18n.category": "main",
"i18n.authors": "@tszngaiyip @strikesnc", "i18n.authors": "@tszngaiyip @strikesnc",
"app.name": "Cider", "app.name": "Cider",
"date.format": "${y}年${m}月${d}日", "date.format": "${y}年${m}月${d}日",
"dialog.cancel": "取消", "dialog.cancel": "取消",
"dialog.ok": "確認", "dialog.ok": "確認",
"notification.updatingLibrarySongs": "更新緊資料庫嘅歌曲...", "notification.updatingLibrarySongs": "更新緊資料庫嘅歌曲...",
"notification.updatingLibraryAlbums": "更新緊資料庫嘅專輯...", "notification.updatingLibraryAlbums": "更新緊資料庫嘅專輯...",
"notification.updatingLibraryArtists": "更新緊資料庫嘅藝人...", "notification.updatingLibraryArtists": "更新緊資料庫嘅藝人...",
"term.appleInc": "Apple Inc.", "term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music", "term.appleMusic": "Apple Music",
"term.applePodcasts": "Apple Podcasts", "term.applePodcasts": "Apple Podcasts",
"term.itunes": "iTunes", "term.itunes": "iTunes",
"term.github": "GitHub", "term.github": "GitHub",
"term.discord": "Discord", "term.discord": "Discord",
"term.learnMore": "想知更多", "term.learnMore": "想知更多",
"term.accountSettings": "帳號設定", "term.accountSettings": "帳號設定",
"term.logout": "登出", "term.logout": "登出",
"term.login": "登入", "term.login": "登入",
"term.about": "關於", "term.about": "關於",
"term.privateSession": "無痕模式", "term.privateSession": "無痕模式",
"term.queue": "待播清單", "term.queue": "待播清單",
"term.history": "播放歷史", "term.history": "播放歷史",
"term.search": "搵野", "term.search": "搵野",
"term.library": "資料庫", "term.library": "資料庫",
"term.listenNow": "即刻聽", "term.listenNow": "即刻聽",
"term.browse": "瀏覽", "term.browse": "瀏覽",
"term.radio": "電台", "term.radio": "電台",
"term.recentlyAdded": "最近加入", "term.recentlyAdded": "最近加入",
"term.songs": "歌曲", "term.songs": "歌曲",
"term.albums": "專輯", "term.albums": "專輯",
"term.artists": "藝人", "term.artists": "藝人",
"term.podcasts": "Podcasts", "term.podcasts": "Podcasts",
"term.playlists": "播放清單", "term.playlists": "播放清單",
"term.playlist": "播放清單", "term.playlist": "播放清單",
"term.newPlaylist": "新播放清單", "term.newPlaylist": "新播放清單",
"term.newPlaylistFolder": "新資料夾", "term.newPlaylistFolder": "新資料夾",
"term.createNewPlaylist": "新增播放清單", "term.createNewPlaylist": "新增播放清單",
"term.createNewPlaylistFolder": "新增資料夾", "term.createNewPlaylistFolder": "新增資料夾",
"term.deletePlaylist": "你係咪要刪除呢個播放清單?", "term.deletePlaylist": "你係咪要刪除呢個播放清單?",
"term.play": "播放", "term.play": "播放",
"term.pause": "暫停", "term.pause": "暫停",
"term.previous": "前一首", "term.previous": "前一首",
"term.next": "下一首", "term.next": "下一首",
"term.shuffle": "隨機播放", "term.shuffle": "隨機播放",
"term.repeat": "重複播放", "term.repeat": "重複播放",
"term.volume": "音量", "term.volume": "音量",
"term.mute": "靜音", "term.mute": "靜音",
"term.unmute": "取消靜音", "term.unmute": "取消靜音",
"term.share": "分享", "term.share": "分享",
"term.share.success": "複製咗喺剪貼簿", "term.share.success": "複製咗喺剪貼簿",
"term.settings": "設定", "term.settings": "設定",
"term.seeAll": "睇哂全部", "term.seeAll": "睇哂全部",
"term.sortBy": "排序", "term.sortBy": "排序",
"term.sortBy.album": "專輯", "term.sortBy.album": "專輯",
"term.sortBy.artist": "藝人", "term.sortBy.artist": "藝人",
"term.sortBy.name": "歌名", "term.sortBy.name": "歌名",
"term.sortBy.genre": "音樂風格", "term.sortBy.genre": "音樂風格",
"term.sortBy.releaseDate": "幾時出", "term.sortBy.releaseDate": "幾時出",
"term.sortBy.duration": "幾長", "term.sortBy.duration": "幾長",
"term.sortOrder": "點排", "term.sortOrder": "點排",
"term.sortOrder.ascending": "順序", "term.sortOrder.ascending": "順序",
"term.sortOrder.descending": "倒序", "term.sortOrder.descending": "倒序",
"term.viewAs": "想點樣顯示", "term.viewAs": "想點樣顯示",
"term.viewAs.coverArt": "專輯封面", "term.viewAs.coverArt": "專輯封面",
"term.viewAs.list": "列表", "term.viewAs.list": "列表",
"term.size": "大細", "term.size": "大細",
"term.size.normal": "正常", "term.size.normal": "正常",
"term.size.compact": "迫啲", "term.size.compact": "迫啲",
"term.enable": "開", "term.enable": "開",
"term.disable": "熄", "term.disable": "熄",
"term.enabled": "開左", "term.enabled": "開左",
"term.disabled": "熄左", "term.disabled": "熄左",
"term.connect": "連結", "term.connect": "連結",
"term.connecting": "連緊", "term.connecting": "連緊",
"term.disconnect": "取消連結", "term.disconnect": "取消連結",
"term.authed": "授權咗", "term.authed": "授權咗",
"term.confirm": "確認?", "term.confirm": "確認?",
"term.more": "多啲", "term.more": "多啲",
"term.less": "少啲", "term.less": "少啲",
"term.showMore": "顯示多啲", "term.showMore": "顯示多啲",
"term.showLess": "顯示少啲", "term.showLess": "顯示少啲",
"term.topSongs": "熱門歌曲", "term.topSongs": "熱門歌曲",
"term.latestReleases": "最新出嘅", "term.latestReleases": "最新出嘅",
"term.time.added": "加入於", "term.time.added": "加入於",
"term.time.released": "發行於", "term.time.released": "發行於",
"term.time.updated": "更新於", "term.time.updated": "更新於",
"term.time.hours": "粒鐘", "term.time.hours": "粒鐘",
"term.time.hour": "粒鐘", "term.time.hour": "粒鐘",
"term.time.minutes": "分鐘", "term.time.minutes": "分鐘",
"term.time.minute": "分鐘", "term.time.minute": "分鐘",
"term.time.seconds": "秒", "term.time.seconds": "秒",
"term.time.second": "秒", "term.time.second": "秒",
"term.fullscreenView": "用全螢幕睇", "term.fullscreenView": "用全螢幕睇",
"term.defaultView": "平時咁睇", "term.defaultView": "平時咁睇",
"term.audioSettings": "音訊設定", "term.audioSettings": "音訊設定",
"term.clearAll": "清除", "term.clearAll": "清除",
"term.recentStations": "呢排聽緊嘅", "term.recentStations": "呢排聽緊嘅",
"term.language": "語言", "term.language": "語言",
"term.funLanguages": "惡搞", "term.funLanguages": "惡搞",
"term.noLyrics": "搵緊... / 搵唔到歌詞。 / 純音樂黎。", "term.noLyrics": "搵緊... / 搵唔到歌詞。 / 純音樂黎。",
"term.copyright": "版權", "term.copyright": "版權",
"term.rightsReserved": "保留一切權利", "term.rightsReserved": "保留一切權利",
"term.sponsor": "課金俾呢個Project", "term.sponsor": "課金俾呢個Project",
"term.ciderTeam": "Cider 團隊", "term.ciderTeam": "Cider 團隊",
"term.developer": "開發者", "term.developer": "開發者",
"term.socialTeam": "PR", "term.socialTeam": "PR",
"term.socials": "我哋嘅社群", "term.socials": "我哋嘅社群",
"term.contributors": "合作人", "term.contributors": "合作人",
"term.equalizer": "均衡器 (EQ)", "term.equalizer": "均衡器 (EQ)",
"term.reset": "重設", "term.reset": "重設",
"term.tracks": "首歌", "term.tracks": "首歌",
"term.videos": "影片", "term.videos": "影片",
"term.menu": "選項", "term.menu": "選項",
"term.check": "檢查", "term.check": "檢查",
"term.aboutArtist": "關於 {{artistName}}", "term.aboutArtist": "關於 {{artistName}}",
"term.topResult": "熱門搜尋結果", "term.topResult": "熱門搜尋結果",
"term.sharedPlaylists": "播放清單", "term.sharedPlaylists": "播放清單",
"term.people": "個人檔案", "term.people": "個人檔案",
"term.newpreset.name": "新EQ範本嘅名", "term.newpreset.name": "新EQ範本嘅名",
"term.addedpreset": "新增咗", "term.addedpreset": "新增咗",
"term.deletepreset.warn": "你係咪要刪除呢個範本?", "term.deletepreset.warn": "你係咪要刪除呢個範本?",
"term.deletedpreset": "刪除咗", "term.deletedpreset": "刪除咗",
"term.musicVideos": "MV", "term.musicVideos": "MV",
"term.stations": "電台", "term.stations": "電台",
"term.radioShows": "電台單集", "term.radioShows": "電台單集",
"term.recordLabels": "唱片公司", "term.recordLabels": "唱片公司",
"term.videoExtras": "相關嘅片", "term.videoExtras": "相關嘅片",
"home.title": "主頁", "home.title": "主頁",
"home.recentlyPlayed": "呢排播左", "home.recentlyPlayed": "呢排播左",
"home.recentlyAdded": "呢排加嘅", "home.recentlyAdded": "呢排加嘅",
"home.artistsFeed": "藝人動態", "home.artistsFeed": "藝人動態",
"home.artistsFeed.noArtist": "Follow 一啲藝人嚟獲得佢哋嘅最新歌曲資訊。 ", "home.artistsFeed.noArtist": "Follow 一啲藝人嚟獲得佢哋嘅最新歌曲資訊。 ",
"home.madeForYou": "為你而整", "home.madeForYou": "為你而整",
"home.friendsListeningTo": "你啲Friend聽緊", "home.friendsListeningTo": "你啲Friend聽緊",
"home.followedArtists": "Follow左嘅藝人", "home.followedArtists": "Follow左嘅藝人",
"error.appleMusicSubRequired": "需要訂閱Apple Music先可以用Cider。", "error.appleMusicSubRequired": "需要訂閱Apple Music先可以用Cider。",
"error.connectionError": "連接唔到Apple Music。", "error.connectionError": "連接唔到Apple Music。",
"error.noResults": "冇結果。", "error.noResults": "冇結果。",
"error.noResults.description": "重新搵過啦。", "error.noResults.description": "重新搵過啦。",
"podcast.followOnCider": "喺Cider上Follow", "podcast.followOnCider": "喺Cider上Follow",
"podcast.followedOnCider": "喺Cider上Follow左", "podcast.followedOnCider": "喺Cider上Follow左",
"podcast.subscribeOnItunes": "喺iTunes上訂閱", "podcast.subscribeOnItunes": "喺iTunes上訂閱",
"podcast.subscribedOnItunes": "喺iTunes上訂閱左", "podcast.subscribedOnItunes": "喺iTunes上訂閱左",
"podcast.itunesStore": "iTunes Store", "podcast.itunesStore": "iTunes Store",
"podcast.episodes": "單集", "podcast.episodes": "單集",
"podcast.playEpisode": "播呢集", "podcast.playEpisode": "播呢集",
"podcast.website": "Podcast 網頁", "podcast.website": "Podcast 網頁",
"action.addToLibrary": "加入資料庫", "action.addToLibrary": "加入資料庫",
"action.addToLibrary.success": "加入咗資料庫", "action.addToLibrary.success": "加入咗資料庫",
"action.addToLibrary.error": "加入唔到資料庫", "action.addToLibrary.error": "加入唔到資料庫",
"action.removeFromLibrary": "喺資料庫到刪除", "action.removeFromLibrary": "喺資料庫到刪除",
"action.removeFromLibrary.success": "已經喺資料庫到刪除咗", "action.removeFromLibrary.success": "已經喺資料庫到刪除咗",
"action.addToQueue": "加入待播清單", "action.addToQueue": "加入待播清單",
"action.addToQueue.success": "加入咗待播清單", "action.addToQueue.success": "加入咗待播清單",
"action.addToQueue.error": "加入唔到待播清單", "action.addToQueue.error": "加入唔到待播清單",
"action.removeFromQueue": "喺待播清單刪除", "action.removeFromQueue": "喺待播清單刪除",
"action.removeFromQueue.success": "已經喺待播清單到刪除咗", "action.removeFromQueue.success": "已經喺待播清單到刪除咗",
"action.removeFromQueue.error": "喺待播清單到刪除唔到", "action.removeFromQueue.error": "喺待播清單到刪除唔到",
"action.createPlaylist": "建立新嘅播放清單", "action.createPlaylist": "建立新嘅播放清單",
"action.addToPlaylist": "加入播放清單", "action.addToPlaylist": "加入播放清單",
"action.removeFromPlaylist": "喺播放清單到刪除", "action.removeFromPlaylist": "喺播放清單到刪除",
"action.addToFavorites": "加至收藏", "action.addToFavorites": "加至收藏",
"action.follow": "Follow", "action.follow": "Follow",
"action.follow.success": "Follow緊", "action.follow.success": "Follow緊",
"action.follow.error": "Follow唔到", "action.follow.error": "Follow唔到",
"action.unfollow": "Unfollow", "action.unfollow": "Unfollow",
"action.unfollow.success": "Unfollow咗", "action.unfollow.success": "Unfollow咗",
"action.unfollow.error": "Unfollow唔到", "action.unfollow.error": "Unfollow唔到",
"action.playNext": "下首即刻播", "action.playNext": "下首即刻播",
"action.playLater": "陣間先再播", "action.playLater": "陣間先再播",
"action.startRadio": "建立電台", "action.startRadio": "建立電台",
"action.goToArtist": "前往藝人", "action.goToArtist": "前往藝人",
"action.goToAlbum": "前往專輯", "action.goToAlbum": "前往專輯",
"action.moveToTop": "返最頂", "action.moveToTop": "返最頂",
"action.share": "分享歌曲", "action.share": "分享歌曲",
"action.rename": "重新命名", "action.rename": "重新命名",
"action.love": "鐘意", "action.love": "鐘意",
"action.unlove": "唔鐘意", "action.unlove": "唔鐘意",
"action.dislike": "唔想再睇到", "action.dislike": "唔想再睇到",
"action.undoDislike": "還原唔想再睇到", "action.undoDislike": "還原唔想再睇到",
"action.showWebRemoteQR": "遙距控制", "action.showWebRemoteQR": "遙距控制",
"action.playTracksNext": "插播 ${app.selectedMediaItems.length} 首歌曲", "action.playTracksNext": "插播 ${app.selectedMediaItems.length} 首歌曲",
"action.playTracksLater": "陣間播放 ${app.selectedMediaItems.length} 首歌曲", "action.playTracksLater": "陣間播放 ${app.selectedMediaItems.length} 首歌曲",
"action.removeTracks": "喺待播清單到刪除 ${self.selectedItems.length} 首歌曲", "action.removeTracks": "喺待播清單到刪除 ${self.selectedItems.length} 首歌曲",
"action.import": "匯入", "action.import": "匯入",
"action.export": "匯出", "action.export": "匯出",
"action.showAlbum": "顯示完整嘅專輯", "action.showAlbum": "顯示完整嘅專輯",
"action.tray.minimize": "收埋喺系統托盤", "action.tray.minimize": "收埋喺系統托盤",
"action.tray.quit": "結束", "action.tray.quit": "結束",
"action.update": "更新", "action.update": "更新",
"action.copy": "複製", "action.copy": "複製",
"action.newpreset": "新增範本", "action.newpreset": "新增範本",
"action.deletepreset": "刪除範本", "action.deletepreset": "刪除範本",
"settings.header.general": "一般", "settings.header.general": "一般",
"settings.header.general.description": "調整Cider嘅一般設定", "settings.header.general.description": "調整Cider嘅一般設定",
"settings.option.general.language": "語言", "settings.option.general.language": "語言",
"settings.option.general.language.main": "語言", "settings.option.general.language.main": "語言",
"settings.option.general.language.fun": "惡搞語言", "settings.option.general.language.fun": "惡搞語言",
"settings.option.general.language.unsorted": "未分類", "settings.option.general.language.unsorted": "未分類",
"settings.header.audio": "音訊", "settings.header.audio": "音訊",
"settings.header.audio.description": "調整Cider嘅音訊設定", "settings.header.audio.description": "調整Cider嘅音訊設定",
"settings.option.audio.quality": "音質", "settings.option.audio.quality": "音質",
"settings.header.audio.quality.high": "質素優先", "settings.header.audio.quality.high": "質素優先",
"settings.option.audio.seamlessTransition": "無縫播放", "settings.option.audio.seamlessTransition": "無縫播放",
"settings.option.audio.enableAdvancedFunctionality": "進階功能", "settings.option.audio.enableAdvancedFunctionality": "進階功能",
"settings.option.audio.enableAdvancedFunctionality.description": "啟用AudioContext解鎖類似音量平衡和均衡器嘅進階功能但係會喺部分電腦造成音樂Lag機。", "settings.option.audio.enableAdvancedFunctionality.description": "啟用AudioContext解鎖類似音量平衡和均衡器嘅進階功能但係會喺部分電腦造成音樂Lag機。",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "音量平衡", "settings.option.audio.enableAdvancedFunctionality.audioNormalization": "音量平衡",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "平衡輕柔同響亮嘅歌曲,令你有統一嘅聆聽體驗。", "settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "平衡輕柔同響亮嘅歌曲,令你有統一嘅聆聽體驗。",
"settings.header.visual": "外觀", "settings.header.visual": "外觀",
"settings.header.visual.description": "調整Cider嘅外觀", "settings.header.visual.description": "調整Cider嘅外觀",
"settings.option.visual.windowBackgroundStyle": "視窗背景樣式", "settings.option.visual.windowBackgroundStyle": "視窗背景樣式",
"settings.header.visual.windowBackgroundStyle.none": "空白", "settings.header.visual.windowBackgroundStyle.none": "空白",
"settings.header.visual.windowBackgroundStyle.artwork": "專輯封面", "settings.header.visual.windowBackgroundStyle.artwork": "專輯封面",
"settings.header.visual.windowBackgroundStyle.image": "圖片", "settings.header.visual.windowBackgroundStyle.image": "圖片",
"settings.option.visual.animatedArtwork": "動態專輯封面", "settings.option.visual.animatedArtwork": "動態專輯封面",
"settings.header.visual.animatedArtwork.always": "總是顯示", "settings.header.visual.animatedArtwork.always": "總是顯示",
"settings.header.visual.animatedArtwork.limited": "淨係喺藝人頁面同專輯封面顯示", "settings.header.visual.animatedArtwork.limited": "淨係喺藝人頁面同專輯封面顯示",
"settings.header.visual.animatedArtwork.disable": "熄左佢", "settings.header.visual.animatedArtwork.disable": "熄左佢",
"settings.option.visual.animatedArtworkQuality": "動態專輯封面品質", "settings.option.visual.animatedArtworkQuality": "動態專輯封面品質",
"settings.header.visual.animatedArtworkQuality.low": "低", "settings.header.visual.animatedArtworkQuality.low": "低",
"settings.header.visual.animatedArtworkQuality.medium": "中", "settings.header.visual.animatedArtworkQuality.medium": "中",
"settings.header.visual.animatedArtworkQuality.high": "高", "settings.header.visual.animatedArtworkQuality.high": "高",
"settings.header.visual.animatedArtworkQuality.veryHigh": "非常高", "settings.header.visual.animatedArtworkQuality.veryHigh": "非常高",
"settings.header.visual.animatedArtworkQuality.extreme": "極高", "settings.header.visual.animatedArtworkQuality.extreme": "極高",
"settings.option.visual.animatedWindowBackground": "動態視窗背景", "settings.option.visual.animatedWindowBackground": "動態視窗背景",
"settings.option.visual.hardwareAcceleration": "硬體加速", "settings.option.visual.hardwareAcceleration": "硬體加速",
"settings.option.visual.hardwareAcceleration.description": "需要重開Cider先會生效", "settings.option.visual.hardwareAcceleration.description": "需要重開Cider先會生效",
"settings.header.visual.hardwareAcceleration.default": "預設", "settings.header.visual.hardwareAcceleration.default": "預設",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU", "settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
"settings.header.visual.theme": "主題", "settings.header.visual.theme": "主題",
"settings.option.visual.theme.default": "預設", "settings.option.visual.theme.default": "預設",
"settings.option.visual.theme.dark": "黑色", "settings.option.visual.theme.dark": "黑色",
"settings.option.visual.showPersonalInfo": "顯示個人檔案", "settings.option.visual.showPersonalInfo": "顯示個人檔案",
"settings.header.lyrics": "歌詞", "settings.header.lyrics": "歌詞",
"settings.header.lyrics.description": "調整Cider嘅歌詞設定", "settings.header.lyrics.description": "調整Cider嘅歌詞設定",
"settings.option.lyrics.enableMusixmatch": "啟用 Musixmatch 歌詞", "settings.option.lyrics.enableMusixmatch": "啟用 Musixmatch 歌詞",
"settings.option.lyrics.enableMusixmatchKaraoke": "開啟唱K模式僅限Musixmatch)", "settings.option.lyrics.enableMusixmatchKaraoke": "開啟唱K模式僅限Musixmatch)",
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌詞語言偏好", "settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌詞語言偏好",
"settings.option.lyrics.enableYoutubeLyrics": "播放MV嘅時候用YouTube字幕", "settings.option.lyrics.enableYoutubeLyrics": "播放MV嘅時候用YouTube字幕",
"settings.header.connectivity": "外部連結", "settings.header.connectivity": "外部連結",
"settings.header.connectivity.description": "調整Cider同外部嘅連結", "settings.header.connectivity.description": "調整Cider同外部嘅連結",
"settings.option.connectivity.discordRPC": "Discord 狀態", "settings.option.connectivity.discordRPC": "Discord 狀態",
"settings.option.connectivity.playbackNotifications": "喺播歌嘅時候通知你", "settings.option.connectivity.playbackNotifications": "喺播歌嘅時候通知你",
"settings.option.connectivity.discordRPC.clearOnPause": "暫停時清除 Discord 狀態", "settings.option.connectivity.discordRPC.clearOnPause": "暫停時清除 Discord 狀態",
"settings.option.connectivity.lastfmScrobble": "Last.FM Scrobbling 記錄", "settings.option.connectivity.lastfmScrobble": "Last.FM Scrobbling 記錄",
"settings.option.connectivity.lastfmScrobble.delay": "Last.FM Scrobble 延遲 (%)", "settings.option.connectivity.lastfmScrobble.delay": "Last.FM Scrobble 延遲 (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "啟用 Last.FM 正在播放", "settings.option.connectivity.lastfmScrobble.nowPlaying": "啟用 Last.FM 正在播放",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "從歌名中移除藝人推薦 (Last.FM)", "settings.option.connectivity.lastfmScrobble.removeFeatured": "從歌名中移除藝人推薦 (Last.FM)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (Last.FM)", "settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (Last.FM)",
"settings.header.experimental": "實驗性功能", "settings.header.experimental": "實驗性功能",
"settings.header.experimental.description": "調整Cider嘅實驗性功能", "settings.header.experimental.description": "調整Cider嘅實驗性功能",
"settings.option.experimental.compactUI": "逼啲既 UI", "settings.option.experimental.compactUI": "逼啲既 UI",
"settings.option.window.close_button_hide": "㩒交叉制嚟隱藏 Cider", "settings.option.window.close_button_hide": "㩒交叉制嚟隱藏 Cider",
"spatial.notTurnedOn": "唔該喺設定入面啟用左空間音訊先。", "spatial.notTurnedOn": "唔該喺設定入面啟用左空間音訊先。",
"spatial.spatialProperties": "空間音訊屬性", "spatial.spatialProperties": "空間音訊屬性",
"spatial.width": "幾闊", "spatial.width": "幾闊",
"spatial.height": "幾高", "spatial.height": "幾高",
"spatial.depth": "幾深", "spatial.depth": "幾深",
"spatial.gain": "增益", "spatial.gain": "增益",
"spatial.roomMaterials": "空間材質", "spatial.roomMaterials": "空間材質",
"spatial.roomDimensions": "空間大小", "spatial.roomDimensions": "空間大小",
"spatial.roomPositions": "空間位置", "spatial.roomPositions": "空間位置",
"spatial.setDimensions": "大小設定", "spatial.setDimensions": "大小設定",
"spatial.setPositions": "位置設定", "spatial.setPositions": "位置設定",
"spatial.up": "上面", "spatial.up": "上面",
"spatial.front": "前面", "spatial.front": "前面",
"spatial.left": "左邊", "spatial.left": "左邊",
"spatial.right": "右邊", "spatial.right": "右邊",
"spatial.back": "後面", "spatial.back": "後面",
"spatial.down": "下面", "spatial.down": "下面",
"spatial.listener": "觀眾", "spatial.listener": "觀眾",
"spatial.audioSource": "音源", "spatial.audioSource": "音源",
"settings.header.unfinished": "未搞掂", "settings.header.unfinished": "未搞掂",
"remote.web.title": "遙距控制 Cider", "remote.web.title": "遙距控制 Cider",
"remote.web.description": "Scan 呢個 QR Code 去控制 Cider", "remote.web.description": "Scan 呢個 QR Code 去控制 Cider",
"about.thanks": "多謝 Cider Collective 同埋所有合作人作出嘅貢獻。" "about.thanks": "多謝 Cider Collective 同埋所有合作人作出嘅貢獻。"
} }

View file

@ -1,8 +1,8 @@
import {app, Menu, nativeImage, Tray, ipcMain, clipboard, shell} from 'electron'; import { app, Menu, nativeImage, Tray, ipcMain, clipboard, shell } from "electron";
import {readFileSync} from "fs"; import { readFileSync } from "fs";
import * as path from 'path'; import * as path from "path";
import * as log from 'electron-log'; import * as log from "electron-log";
import {utils} from './utils'; import { utils } from "./utils";
/** /**
* @file Creates App instance * @file Creates App instance
@ -11,317 +11,314 @@ import {utils} from './utils';
/** @namespace */ /** @namespace */
export class AppEvents { export class AppEvents {
private protocols: string[] = [ private protocols: string[] = ["ame", "cider", "itms", "itmss", "musics", "music"];
"ame", private plugin: any = undefined;
"cider", private tray: any = undefined;
"itms", private i18n: any = undefined;
"itmss",
"musics",
"music"
]
private plugin: any = undefined;
private tray: any = undefined;
private i18n: any = undefined;
/** @constructor */ /** @constructor */
constructor() { constructor() {
this.start(); 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();
} }
/** // Verbose Check
* Handles all actions that occur for the app on start (Mainly commandline arguments) if (app.commandLine.hasSwitch("verbose")) {
* @returns {void} console.log("[Cider] User has launched the application with --verbose");
*/
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)
})
}
} }
public quit() { // Log File Location
console.log('[AppEvents] App quit'); if (app.commandLine.hasSwitch("log") || app.commandLine.hasSwitch("l")) {
console.log(path.join(app.getPath("userData"), "logs"));
app.exit();
} }
public ready(plug: any) { // Try limiting JS memory to 350MB.
this.plugin = plug app.commandLine.appendSwitch("js-flags", "--max-old-space-size=350");
console.log('[AppEvents] App ready');
AppEvents.setLoginSettings() // Expose GC
} app.commandLine.appendSwitch("js-flags", "--expose_gc");
public bwCreated() { if (process.platform === "win32") {
app.on('open-url', (event, url) => { app.setAppUserModelId(app.getName()); // For notification name
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 * 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":
* Handles links (URI) and protocols for the application console.info("WebGPU is enabled.");
* @param arg app.commandLine.appendSwitch("enable-unsafe-webgpu");
*/ break;
private LinkHandler(arg: string) {
if (!arg) return;
// LastFM Auth URL case "disabled":
if (arg.includes('auth')) { console.info("Hardware acceleration is disabled.");
const authURI = arg.split('/auth/')[1] app.commandLine.appendSwitch("disable-gpu");
if (authURI.startsWith('lastfm')) { // If we wanted more auth options break;
console.log('token: ', authURI.split('lastfm?token=')[1]) }
utils.getWindow().webContents.executeJavaScript(`ipcRenderer.send('lastfm:auth', "${authURI.split('lastfm?token=')[1]}")`).catch(console.error)
} 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 } else if (arg.includes("music.apple.com")) {
utils.getWindow().webContents.executeJavaScript('MusicKitInterop.playPause()') // URL (used with itms/itmss/music/musics uris)
} console.log(arg);
else if (arg.includes('nextitem')) { let url = arg.split("//")[1];
//language=JS console.warn(`[LinkHandler] Attempting to load url: ${url}`);
utils.getWindow().webContents.executeJavaScript('app.mk.skipToNextItem()') utils.getWindow().webContents.send("play", "url", url);
} } else if (arg.includes("/debug/appdata")) {
// Play shell.openPath(app.getPath("userData"));
else if (arg.includes('/play/')) { //Steer away from protocol:// specific conditionals } else if (arg.includes("/debug/logs")) {
const playParam = arg.split('/play/')[1] 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", * Handles the creation of a new instance of the app
"a/": "album", */
"p/": "playlist" 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 (!gotTheLock) {
if (playParam.includes(key)) { // Runs on the new instance if another instance has been found
const id = playParam.split(key)[1] console.log("[Cider] Another instance has been found, quitting.");
utils.getWindow().webContents.send('play', value, id) app.quit();
console.debug(`[LinkHandler] Attempting to load ${value} by id: ${id}`) } 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) startArgs.forEach((arg) => {
console.log(arg) console.log(arg);
let url = arg.split('//')[1] if (arg.includes("cider://")) {
console.warn(`[LinkHandler] Attempting to load url: ${url}`); console.debug("[InstanceHandler] (second-instance) Link detected with " + arg);
utils.getWindow().webContents.send('play', 'url', url) this.LinkHandler(arg);
} else if (arg.includes('/debug/appdata')) { } else if (arg.includes("--force-quit")) {
shell.openPath(app.getPath('userData')) console.warn("[InstanceHandler] (second-instance) Force Quit found. Quitting App.");
} else if (arg.includes('/debug/logs')) { app.quit();
shell.openPath(app.getPath('logs')) } else if (utils.getWindow()) {
} else if (arg.includes('/discord')) { if (utils.getWindow().isMinimized()) utils.getWindow().restore();
shell.openExternal('https://discord.gg/applemusic') utils.getWindow().show();
} else if (arg.includes('/github')) { utils.getWindow().focus();
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() }
/**
* 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 { } else {
utils.getWindow().webContents.executeJavaScript(`app.appRoute('${arg.split('//')[1]}')`) utils.getWindow().show();
} }
} }
});
/** utils.getWindow().on("show", () => {
* Handles the creation of a new instance of the app this.setTray(true);
*/ });
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()
if (!gotTheLock) { // Runs on the new instance if another instance has been found utils.getWindow().on("restore", () => {
console.log('[Cider] Another instance has been found, quitting.') this.setTray(true);
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())
startArgs.forEach(arg => { utils.getWindow().on("hide", () => {
console.log(arg) this.setTray(false);
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("minimize", () => {
this.setTray(false);
});
}
/** /**
* Initializes the applications tray * Sets the tray context menu to a given state
*/ * @param visible - BrowserWindow Visibility
private InitTray() { */
const icons = { private setTray(visible: boolean = utils.getWindow().isVisible()) {
"win32": nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.ico`)).resize({ this.i18n = utils.getLocale(utils.getStoreValue("general.language"));
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', () => { const ciderIcon = nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
if (utils.getWindow()) { width: 24,
if (utils.getWindow().isVisible()) { height: 24,
utils.getWindow().focus() });
} else {
utils.getWindow().show()
}
}
})
utils.getWindow().on('show', () => { const menu = Menu.buildFromTemplate([
this.setTray(true) {
}) label: app.getName(),
enabled: false,
icon: ciderIcon,
},
utils.getWindow().on('restore', () => { { type: "separator" },
this.setTray(true)
})
utils.getWindow().on('hide', () => { /* For now only idea i dont know if posible to implement
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
this could be implemented in a plugin if you would like track info, it would be impractical to put listeners in this file. -Core 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'}, {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']}`), visible: !visible,
click: () => { label: this.i18n["term.next"],
if (utils.getWindow()) { click: () => {
if (visible) { utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.next()`);
utils.getWindow().hide() },
} else { },
utils.getWindow().show()
} {
} visible: !visible,
} label: this.i18n["term.previous"],
}, click: () => {
{ utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.previous()`);
label: this.i18n['term.quit'], },
click: () => { },
app.quit()
} { 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 * Initializes logging in the application
* @private * @private
*/ */
private static initLogging() { private static initLogging() {
log.transports.console.format = '[{h}:{i}:{s}.{ms}] [{level}] {text}'; log.transports.console.format = "[{h}:{i}:{s}.{ms}] [{level}] {text}";
Object.assign(console, log.functions); Object.assign(console, log.functions);
console.debug = function(...args: any[]) { console.debug = function (...args: any[]) {
if (!app.isPackaged) { if (!app.isPackaged) {
log.debug(...args) log.debug(...args);
} }
}; };
ipcMain.on('fetch-log', (_event) => { ipcMain.on("fetch-log", (_event) => {
const data = readFileSync(log.transports.file.getFile().path, {encoding: 'utf8', flag: 'r'}); const data = readFileSync(log.transports.file.getFile().path, {
clipboard.writeText(data) encoding: "utf8",
}) flag: "r",
} });
clipboard.writeText(data);
});
}
/** /**
* Set login settings * Set login settings
* @private * @private
*/ */
private static setLoginSettings() { private static setLoginSettings() {
if (utils.getStoreValue('general.onStartup.enabled')) { if (utils.getStoreValue("general.onStartup.enabled")) {
app.setLoginItemSettings({ app.setLoginItemSettings({
openAtLogin: true, openAtLogin: true,
path: app.getPath('exe'), path: app.getPath("exe"),
args: [`${utils.getStoreValue('general.onStartup.hidden') ? '--hidden' : ''}`] args: [`${utils.getStoreValue("general.onStartup.hidden") ? "--hidden" : ""}`],
}) });
} else { } else {
app.setLoginItemSettings({ app.setLoginItemSettings({
openAtLogin: false, openAtLogin: false,
path: app.getPath('exe') path: app.getPath("exe"),
}) });
}
} }
}
} }

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,10 @@
var util = require('util'); var util = require("util");
var castv2Cli = require('castv2-client'); var castv2Cli = require("castv2-client");
var RequestResponseController = castv2Cli.RequestResponseController; var RequestResponseController = castv2Cli.RequestResponseController;
function CiderCastController(client, sourceId, destinationId) { function CiderCastController(client, sourceId, destinationId) {
RequestResponseController.call(this, client, sourceId, destinationId, 'urn:x-cast:com.ciderapp.customdata'); RequestResponseController.call(this, client, sourceId, destinationId, "urn:x-cast:com.ciderapp.customdata");
this.once('close', onclose); this.once("close", onclose);
var self = this; var self = this;
function onclose() { function onclose() {
self.stop(); self.stop();
@ -13,20 +13,20 @@ function CiderCastController(client, sourceId, destinationId) {
util.inherits(CiderCastController, RequestResponseController); util.inherits(CiderCastController, RequestResponseController);
CiderCastController.prototype.sendIp = function(ip) { CiderCastController.prototype.sendIp = function (ip) {
// TODO: Implement Callback // TODO: Implement Callback
let data = { let data = {
ip : ip ip: ip,
} };
this.request(data); this.request(data);
}; };
CiderCastController.prototype.kill = function() { CiderCastController.prototype.kill = function () {
// TODO: Implement Callback // TODO: Implement Callback
let data = { let data = {
action : "stop" action: "stop",
} };
this.request(data); this.request(data);
}; };
module.exports = CiderCastController; module.exports = CiderCastController;

View file

@ -1,9 +1,9 @@
//@ts-nocheck //@ts-nocheck
var util = require('util'); var util = require("util");
// var debug = require('debug')('castv2-client'); // var debug = require('debug')('castv2-client');
var Application = require('castv2-client').Application; var Application = require("castv2-client").Application;
var MediaController = require('castv2-client').MediaController; var MediaController = require("castv2-client").MediaController;
var CiderCastController = require('./castcontroller'); var CiderCastController = require("./castcontroller");
function CiderReceiver(client, session) { function CiderReceiver(client, session) {
Application.apply(this, arguments); Application.apply(this, arguments);
@ -11,70 +11,69 @@ function CiderReceiver(client, session) {
this.media = this.createController(MediaController); this.media = this.createController(MediaController);
this.mediaReceiver = this.createController(CiderCastController); this.mediaReceiver = this.createController(CiderCastController);
this.media.on('status', onstatus); this.media.on("status", onstatus);
var self = this; var self = this;
function onstatus(status) { function onstatus(status) {
self.emit('status', status); self.emit("status", status);
} }
} }
// FE96A351 // FE96A351
// 27E1334F // 27E1334F
CiderReceiver.APP_ID = 'FE96A351'; CiderReceiver.APP_ID = "FE96A351";
util.inherits(CiderReceiver, Application); util.inherits(CiderReceiver, Application);
CiderReceiver.prototype.getStatus = function(callback) { CiderReceiver.prototype.getStatus = function (callback) {
this.media.getStatus.apply(this.media, arguments); 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); this.media.load.apply(this.media, arguments);
}; };
CiderReceiver.prototype.play = function(callback) { CiderReceiver.prototype.play = function (callback) {
this.media.play.apply(this.media, arguments); this.media.play.apply(this.media, arguments);
}; };
CiderReceiver.prototype.pause = function(callback) { CiderReceiver.prototype.pause = function (callback) {
this.media.pause.apply(this.media, arguments); this.media.pause.apply(this.media, arguments);
}; };
CiderReceiver.prototype.stop = function(callback) { CiderReceiver.prototype.stop = function (callback) {
this.media.stop.apply(this.media, arguments); 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); 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); 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); 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); 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); 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); this.media.queueUpdate.apply(this.media, arguments);
}; };
CiderReceiver.prototype.sendIp = function(opts){ CiderReceiver.prototype.sendIp = function (opts) {
this.mediaReceiver.sendIp.apply(this.mediaReceiver, arguments); this.mediaReceiver.sendIp.apply(this.mediaReceiver, arguments);
}; };
CiderReceiver.prototype.kill = function(opts){ CiderReceiver.prototype.kill = function (opts) {
this.mediaReceiver.kill.apply(this.mediaReceiver, arguments); this.mediaReceiver.kill.apply(this.mediaReceiver, arguments);
}; };

View file

@ -1,7 +1,7 @@
import * as fs from 'fs'; import * as fs from "fs";
import * as path from 'path'; import * as path from "path";
import * as electron from 'electron' import * as electron from "electron";
import {utils} from './utils'; import { utils } from "./utils";
// //
// Hello, this is our loader for the various plugins that the Cider Development Team built for our // 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} * @see {@link https://github.com/ciderapp/Cider/wiki/Plugins|Documentation}
*/ */
export class Plugins { export class Plugins {
private static PluginMap: any = {}; private static PluginMap: any = {};
private basePluginsPath = path.join(__dirname, '../plugins'); private basePluginsPath = path.join(__dirname, "../plugins");
private userPluginsPath = path.join(electron.app.getPath('userData'), 'Plugins'); private userPluginsPath = path.join(electron.app.getPath("userData"), "Plugins");
private readonly pluginsList: any = {}; private readonly pluginsList: any = {};
constructor() { constructor() {
this.pluginsList = this.getPlugins(); 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 (fs.existsSync(this.userPluginsPath)) {
if (Plugins.PluginMap[plugin]) { fs.readdirSync(this.userPluginsPath).forEach((file) => {
return Plugins.PluginMap[plugin]; // Plugins V1
} else { if (file.endsWith(".ts") || file.endsWith(".js")) {
return plugin; 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) {
public getPlugins(): any { console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
let plugins: any = {}; } else {
plugins[file] = new plugin(utils);
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)
}
} }
} 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()) {
public callPlugin(plugin: string, event: string, ...args: any[]) { const pluginPath = path.join(this.userPluginsPath, file);
if (this.pluginsList[plugin][event]) { if (fs.existsSync(path.join(pluginPath, "package.json"))) {
this.pluginsList[plugin][event](...args); 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);
}
}
} }

View file

@ -1,374 +1,320 @@
import * as ElectronStore from 'electron-store'; import * as ElectronStore from "electron-store";
import * as electron from "electron"; import * as electron from "electron";
import {app} from "electron"; import { app } from "electron";
import fetch from "electron-fetch"; import fetch from "electron-fetch";
export class Store { export class Store {
static cfg: ElectronStore; static cfg: ElectronStore;
private defaults: any = { private defaults: any = {
"main": { main: {
"PLATFORM": process.platform, PLATFORM: process.platform,
"UPDATABLE": app.isPackaged && (!process.mas || !process.windowsStore || !process.env.FLATPAK_ID) 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 home: {
"playbackNotifications": true, followedArtists: [],
"resumeOnStartupBehavior": "local", favoriteItems: [],
"privateEnabled": false, },
"themeUpdateNotification": true, libraryPrefs: {
"sidebarItems": { songs: {
"recentlyAdded": true, scroll: "paged",
"songs": true, sort: "name",
"albums": true, sortOrder: "asc",
"artists": true, size: "normal",
"videos": true, },
"podcasts": true albums: {
}, scroll: "paged",
"sidebarCollapsed": { sort: "name",
"cider": false, sortOrder: "asc",
"applemusic": false, viewAs: "covers",
"library": false, },
"amplaylists": false, playlists: {
"playlists": false, scroll: "infinite",
"localLibrary": false },
}, localPaths: [],
"onStartup": { pageSize: 250,
"enabled": false, },
"hidden": false, audio: {
}, volume: 1,
"resumeTabs": { volumeStep: 0.05,
"tab": "home", maxVolume: 1,
"dynamicData": "" lastVolume: 1,
}, muted: false,
"keybindings": { playbackRate: 1,
"search": [ quality: "HIGH",
"CommandOrControl", seamless_audio: true,
"F" normalization: true,
], dBSPL: false,
"listnow": [ dBSPLcalibration: 90,
"CommandOrControl", maikiwiAudio: {
"L" ciderPPE: true,
], ciderPPE_value: "MAIKIWI",
"browse": [ opportunisticCorrection_state: "OFF",
"CommandOrControl", atmosphereRealizer1: false,
"B" atmosphereRealizer1_value: "NATURAL_STANDARD",
], atmosphereRealizer2: false,
"recentAdd": [ atmosphereRealizer2_value: "NATURAL_STANDARD",
"CommandOrControl", spatial: false,
"G" spatialProfile: "BPLK",
], vibrantBass: {
"songs": [ // Hard coded into the app. Don't include any of this config into exporting presets in store.ts
"CommandOrControl", 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],
"J" 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],
"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": { spatial: false,
"enabled": true, spatial_properties: {
"client": "Cider", presets: [],
"clear_on_pause": true, gain: 0.8,
"hide_buttons": false, listener_position: [0, 0, 0],
"hide_timestamp": false, audio_position: [0, 0, 0],
"state_format": "by {artist}", room_dimensions: {
"details_format": "{title}", width: 32,
}, height: 12,
"lastfm": { depth: 32,
"enabled": false, },
"scrobble_after": 50, room_materials: {
"filter_loop": false, left: "metal",
"filter_types": {}, right: "metal",
"remove_featured": false, front: "brick-bare",
"secrets": { back: "brick-bare",
"username": "", down: "acoustic-ceiling-tiles",
"key": "" 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",
},
};
}, constructor() {
}, Store.cfg = new ElectronStore({
"home": { name: "cider-config",
"followedArtists": [], defaults: this.defaults,
"favoriteItems": [] schema: this.schema,
}, migrations: this.migrations,
"libraryPrefs": { clearInvalidConfig: false, //disabled for now
"songs": { });
"scroll": "paged",
"sort": "name", Store.cfg.set(this.mergeStore(this.defaults, Store.cfg.store));
"sortOrder": "asc", this.ipcHandler();
"size": "normal" }
},
"albums": { static pushToCloud(): void {
"scroll": "paged", if (Store.cfg.get("connectUser.auth") === null) return;
"sort": "name", var syncData = Object();
"sortOrder": "asc", if (Store.cfg.get("connectUser.sync.themes")) {
"viewAs": "covers" syncData.push({
}, themes: Store.cfg.store.themes,
"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,
}
},
} }
private migrations: any = {} if (Store.cfg.get("connectUser.sync.plugins")) {
private schema: ElectronStore.Schema<any> = { syncData.push({
"connectivity.discord_rpc": { plugins: Store.cfg.store.plugins,
type: 'object' });
},
} }
constructor() { if (Store.cfg.get("connectUser.sync.settings")) {
Store.cfg = new ElectronStore({ syncData.push({
name: 'cider-config', general: Store.cfg.get("general"),
defaults: this.defaults, home: Store.cfg.get("home"),
schema: this.schema, libraryPrefs: Store.cfg.get("libraryPrefs"),
migrations: this.migrations, advanced: Store.cfg.get("advanced"),
clearInvalidConfig: false //disabled for now });
});
Store.cfg.set(this.mergeStore(this.defaults, Store.cfg.store))
this.ipcHandler();
} }
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 { fetch("https://connect.cidercollective.dev/api/v1/setttings/set", {
if (Store.cfg.get('connectUser.auth') === null) return; method: "POST",
var syncData = Object(); headers: {
if (Store.cfg.get('connectUser.sync.themes')) { "Content-Type": "application/json",
syncData.push({ },
themes: Store.cfg.store.themes body: JSON.stringify(postBody),
}) });
} }
if (Store.cfg.get('connectUser.sync.plugins')) {
syncData.push({
plugins: Store.cfg.store.plugins
})
}
if (Store.cfg.get('connectUser.sync.settings')) { /**
syncData.push({ * Merge Configurations
general: Store.cfg.get('general'), * @param target The target configuration
home: Store.cfg.get('home'), * @param source The source configuration
libraryPrefs: Store.cfg.get('libraryPrefs'), */
advanced: Store.cfg.get('advanced'), 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)) {
let postBody = { if (key.includes("migrations")) {
id: Store.cfg.get('connectUser.id'), continue;
app: electron.app.getName(), }
version: electron.app.isPackaged ? electron.app.getVersion() : 'dev', if (source[key] instanceof Array) {
syncData: syncData continue;
} }
if (source[key] instanceof Object) Object.assign(source[key], this.mergeStore(target[key], source[key]));
fetch('https://connect.cidercollective.dev/api/v1/setttings/set', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postBody)
})
} }
// Join `target` and modified `source`
Object.assign(target || {}, source);
return target;
};
/** /**
* Merge Configurations * IPC Handler
* @param target The target configuration */
* @param source The source configuration private ipcHandler(): void {
*/ electron.ipcMain.handle("getStoreValue", (_event, key, defaultValue) => {
private mergeStore = (target: { [x: string]: any; }, source: { [x: string]: any; }) => { return defaultValue ? Store.cfg.get(key, true) : Store.cfg.get(key);
// 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
}
/** electron.ipcMain.handle("setStoreValue", (_event, key, value) => {
* IPC Handler Store.cfg.set(key, value);
*/ });
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) => { electron.ipcMain.on("getStore", (event) => {
Store.cfg.set(key, value); event.returnValue = Store.cfg.store;
}); });
electron.ipcMain.on('getStore', (event) => { electron.ipcMain.on("setStore", (_event, store) => {
event.returnValue = Store.cfg.store Store.cfg.store = store;
}) });
}
electron.ipcMain.on('setStore', (_event, store) => {
Store.cfg.store = store
})
}
} }

View file

@ -1,97 +1,96 @@
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import {Store} from "./store"; import { Store } from "./store";
import {BrowserWindow as bw} from "./browserwindow"; import { BrowserWindow as bw } from "./browserwindow";
import {app, BrowserWindow, ipcMain} from "electron"; import { app, BrowserWindow, ipcMain } from "electron";
import fetch from "electron-fetch"; import fetch from "electron-fetch";
import ElectronStore from "electron-store"; import ElectronStore from "electron-store";
export class utils { 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 * Get the path
*/ * @returns {string}
static playback = { * @param name
pause: () => { */
bw.win.webContents.executeJavaScript("MusicKitInterop.pause()") static getPath(name: string): string {
}, return this.paths[name];
play: () => { }
bw.win.webContents.executeJavaScript("MusicKitInterop.play()")
}, /**
playPause: () => { * Get the app
bw.win.webContents.executeJavaScript("MusicKitInterop.playPause()") * @returns {Electron.App}
}, */
next: () => { static getApp(): Electron.App {
bw.win.webContents.executeJavaScript("MusicKitInterop.next()") return app;
}, }
previous: () => {
bw.win.webContents.executeJavaScript("MusicKitInterop.previous()") /**
}, * Get the IPCMain
seek: (seconds: number) => { */
bw.win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${seconds})`) 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`))) {
* 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`))) {
fetch(`https://raw.githubusercontent.com/ciderapp/Cider/main/src/i18n/${language}.json`) fetch(`https://raw.githubusercontent.com/ciderapp/Cider/main/src/i18n/${language}.json`)
.then(res => res.json()) .then(res => res.json())
.then(res => { .then(res => {
@ -103,72 +102,70 @@ export class utils {
} }
}) })
} */ } */
if (key) {
return i18n[key]
} else {
return i18n
}
}
/** if (key) {
* Gets a store value return i18n[key];
* @param key } else {
* @returns store value return i18n;
*/
static getStoreValue(key: string): any {
return Store.cfg.get(key)
} }
}
/** /**
* Sets a store * Gets a store value
* @returns store * @param key
*/ * @returns store value
static getStore(): Object { */
return Store.cfg.store 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];
} }
}
/** static loadPluginFrontend(path: string): void {}
* Get the store instance
* @returns {Store}
*/
static getStoreInstance(): ElectronStore {
return Store.cfg
}
/** static loadJSFrontend(path: string): void {
* Sets a store value bw.win.webContents.executeJavaScript(fs.readFileSync(path, "utf8"));
* @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"));
}
} }

View file

@ -64,4 +64,4 @@
"components/artist-chip", "components/artist-chip",
"components/hello-world", "components/hello-world",
"components/inline-collection-list" "components/inline-collection-list"
] ]

View file

@ -100,7 +100,8 @@
"component": "<cider-groupings :data=\"browsepage\"></cider-groupings>", "component": "<cider-groupings :data=\"browsepage\"></cider-groupings>",
"condition": "page == 'groupings'", "condition": "page == 'groupings'",
"onEnter": "" "onEnter": ""
},{ },
{
"page": "charts", "page": "charts",
"component": "<cider-charts :data=\"browsepage\"></cider-charts>", "component": "<cider-charts :data=\"browsepage\"></cider-charts>",
"condition": "page == 'charts'", "condition": "page == 'charts'",
@ -181,4 +182,4 @@
"component": "<replay-page></replay-page>", "component": "<replay-page></replay-page>",
"condition": "page == 'replay'" "condition": "page == 'replay'"
} }
] ]

View file

@ -4,366 +4,421 @@ import * as electron from "electron";
const WebSocketServer = ws.Server; const WebSocketServer = ws.Server;
interface standardResponse { interface standardResponse {
status?: Number, status?: Number;
message?: String, message?: String;
data?: any, data?: any;
type?: string, type?: string;
} }
export class wsapi { export class wsapi {
static clients: any; static clients: any;
port: any = 26369 port: any = 26369;
wss: any = null wss: any = null;
clients: any = [] clients: any = [];
private _win: any; private _win: any;
constructor(win: any) { constructor(win: any) {
this._win = win; 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() { public async InitWebSockets() {
// create random guid electron.ipcMain.on("wsapi-updatePlaybackState", (_event: any, arg: any) => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { this.updatePlaybackState(arg);
var r = Math.random() * 16 | 0, });
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
public async InitWebSockets() { electron.ipcMain.on("wsapi-returnQueue", (_event: any, arg: any) => {
electron.ipcMain.on('wsapi-updatePlaybackState', (_event: any, arg: any) => { this.returnQueue(JSON.parse(arg));
this.updatePlaybackState(arg); });
})
electron.ipcMain.on('wsapi-returnQueue', (_event: any, arg: any) => { electron.ipcMain.on("wsapi-returnSearch", (_event: any, arg: any) => {
this.returnQueue(JSON.parse(arg)); console.log("SEARCH");
}); this.returnSearch(JSON.parse(arg));
});
electron.ipcMain.on('wsapi-returnSearch', (_event: any, arg: any) => { electron.ipcMain.on("wsapi-returnSearchLibrary", (_event: any, arg: any) => {
console.log("SEARCH") this.returnSearchLibrary(JSON.parse(arg));
this.returnSearch(JSON.parse(arg)); });
});
electron.ipcMain.on('wsapi-returnSearchLibrary', (_event: any, arg: any) => { electron.ipcMain.on("wsapi-returnDynamic", (_event: any, arg: any, type: any) => {
this.returnSearchLibrary(JSON.parse(arg)); this.returnDynamic(JSON.parse(arg), type);
}); });
electron.ipcMain.on('wsapi-returnDynamic', (_event: any, arg: any, type: any) => { electron.ipcMain.on("wsapi-returnMusicKitApi", (_event: any, arg: any, method: any) => {
this.returnDynamic(JSON.parse(arg), type); this.returnMusicKitApi(JSON.parse(arg), method);
}); });
electron.ipcMain.on('wsapi-returnMusicKitApi', (_event: any, arg: any, method: any) => { electron.ipcMain.on("wsapi-returnLyrics", (_event: any, arg: any) => {
this.returnMusicKitApi(JSON.parse(arg), method); 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) => { const defaultResponse: standardResponse = {
this.returnLyrics(JSON.parse(arg)); status: 0,
}); data: {},
electron.ipcMain.on('wsapi-returnvolumeMax', (_event: any, arg: any) => { message: "OK",
this.returnmaxVolume(JSON.parse(arg)); type: "generic",
}); };
electron.ipcMain.on('wsapi-libraryStatus', (_event: any, inLibrary: boolean, rating: number) => {
this.returnLibraryStatus(inLibrary, rating); this.wss.on("connection", (ws: any) => {
}); ws.id = this.createId();
electron.ipcMain.on('wsapi-rate', (_event: any, kind: string, id: string, rating: number) => { console.log(`Client ${ws.id} connected`);
this.returnRatingStatus(kind, id, rating); this.clients.push(ws);
}); ws.on("message", function incoming(_message: any) {});
electron.ipcMain.on('wsapi-change-library', (_event: any, kind: string, id: string, shouldAdd: boolean) => { // ws on message
this.returnLibraryChange(kind, id, shouldAdd); ws.on("message", (message: any) => {
}); let data = JSON.parse(message);
this.wss = new WebSocketServer({ let response: standardResponse = {
port: this.port, status: 0,
perMessageDeflate: { data: {},
zlibDeflateOptions: { message: "OK",
// See zlib defaults. type: "generic",
chunkSize: 1024, };
memLevel: 7, if (data.action) {
level: 3 data.action.toLowerCase();
}, }
zlibInflateOptions: { switch (data.action) {
chunkSize: 10 * 1024 default:
}, response.message = "Action not found";
// Other options settable: break;
clientNoContextTakeover: true, // Defaults to negotiated value. case "identify":
serverNoContextTakeover: true, // Defaults to negotiated value. response.message = "Thanks for identifying!";
serverMaxWindowBits: 10, // Defaults to negotiated value. response.data = {
// Below options specified as default values. id: ws.id,
concurrencyLimit: 10, // Limits zlib concurrency for perf. };
threshold: 1024 // Size (in bytes) below which messages ws.identity = {
// should not be compressed if context takeover is disabled. 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;
console.log(`WebSocketServer started on port: ${this.port}`); case "repeat":
this._win.webContents.executeJavaScript(`wsapi.toggleRepeat()`);
const defaultResponse: standardResponse = {status: 0, data: {}, message: "OK", type: "generic"}; break;
case "seek":
this._win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${parseFloat(data.time)})`);
this.wss.on('connection', (ws: any) => { response.message = "Seek";
ws.id = this.createId(); break;
console.log(`Client ${ws.id} connected`) case "pause":
this.clients.push(ws); this._win.webContents.executeJavaScript(`MusicKit.getInstance().pause()`);
ws.on('message', function incoming(_message: any) { response.message = "Paused";
break;
}); case "playpause":
// ws on message this._win.webContents.executeJavaScript(`MusicKitInterop.playPause()`);
ws.on('message', (message: any) => { response.message = "Play/Pause";
let data = JSON.parse(message); break;
let response: standardResponse = {status: 0, data: {}, message: "OK", type: "generic"}; case "play":
if (data.action) { this._win.webContents.executeJavaScript(`MusicKit.getInstance().play()`);
data.action.toLowerCase(); response.message = "Playing";
} break;
switch (data.action) { case "stop":
default: this._win.webContents.executeJavaScript(`MusicKit.getInstance().stop()`);
response.message = "Action not found"; response.message = "Stopped";
break; break;
case "identify": case "volumeMax":
response.message = "Thanks for identifying!" this._win.webContents.executeJavaScript(`wsapi.getmaxVolume()`);
response.data = { response.message = "maxVolume";
id: ws.id break;
} case "volume":
ws.identity = { this._win.webContents.executeJavaScript(`MusicKit.getInstance().volume = ${parseFloat(data.volume)}`);
name: data.name, response.message = "Volume";
author: data.author, break;
description: data.description, case "mute":
version: data.version this._win.webContents.executeJavaScript(`MusicKit.getInstance().mute()`);
} response.message = "Muted";
break; break;
case "play-next": case "unmute":
this._win.webContents.executeJavaScript(`wsapi.playNext(\`${data.type}\`,\`${data.id}\`)`); this._win.webContents.executeJavaScript(`MusicKit.getInstance().unmute()`);
response.message = "Play Next"; response.message = "Unmuted";
break; break;
case "play-later": case "next":
this._win.webContents.executeJavaScript(`wsapi.playLater(\`${data.type}\`,\`${data.id}\`)`); this._win.webContents.executeJavaScript(`if (MusicKit.getInstance().queue.nextPlayableItemIndex != -1 && MusicKit.getInstance().queue.nextPlayableItemIndex != null) {
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) {
try { try {
app.prevButtonBackIndicator = false; app.prevButtonBackIndicator = false;
} catch (e) { } } catch (e) { }
MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.nextPlayableItemIndex);}`); MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.nextPlayableItemIndex);}`);
response.message = "Next"; response.message = "Next";
break; break;
case "previous": case "previous":
this._win.webContents.executeJavaScript(`if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null) {MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex)}`); this._win.webContents.executeJavaScript(
response.message = "Previous"; `if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null) {MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex)}`
break; );
case "musickit-api": response.message = "Previous";
this._win.webContents.executeJavaScript(`wsapi.musickitApi(\`${data.method}\`, \`${data.id}\`, ${JSON.stringify(data.params)} , ${data.library})`); break;
break; case "musickit-api":
case "musickit-library-api": this._win.webContents.executeJavaScript(`wsapi.musickitApi(\`${data.method}\`, \`${data.id}\`, ${JSON.stringify(data.params)} , ${data.library})`);
break; break;
case "set-autoplay": case "musickit-library-api":
this._win.webContents.executeJavaScript(`wsapi.setAutoplay(${data.autoplay})`); break;
break; case "set-autoplay":
case "queue-move": this._win.webContents.executeJavaScript(`wsapi.setAutoplay(${data.autoplay})`);
this._win.webContents.executeJavaScript(`wsapi.moveQueueItem(${data.from},${data.to})`); break;
break; case "queue-move":
case "get-queue": this._win.webContents.executeJavaScript(`wsapi.moveQueueItem(${data.from},${data.to})`);
this._win.webContents.executeJavaScript(`wsapi.getQueue()`); break;
break; case "get-queue":
case "search": this._win.webContents.executeJavaScript(`wsapi.getQueue()`);
if (!data.limit) { break;
data.limit = 10; case "search":
} if (!data.limit) {
this._win.webContents.executeJavaScript(`wsapi.search(\`${data.term}\`, \`${data.limit}\`)`); data.limit = 10;
break; }
case "library-search": this._win.webContents.executeJavaScript(`wsapi.search(\`${data.term}\`, \`${data.limit}\`)`);
if (!data.limit) { break;
data.limit = 10; case "library-search":
} if (!data.limit) {
this._win.webContents.executeJavaScript(`wsapi.searchLibrary(\`${data.term}\`, \`${data.limit}\`)`); data.limit = 10;
break; }
case "show-window": this._win.webContents.executeJavaScript(`wsapi.searchLibrary(\`${data.term}\`, \`${data.limit}\`)`);
this._win.show() break;
break; case "show-window":
case "hide-window": this._win.show();
this._win.hide() break;
break; case "hide-window":
case "play-mediaitem": this._win.hide();
this._win.webContents.executeJavaScript(`wsapi.playTrackById("${data.id}", \`${data.kind}\`)`); break;
response.message = "Playing track"; case "play-mediaitem":
break; this._win.webContents.executeJavaScript(`wsapi.playTrackById("${data.id}", \`${data.kind}\`)`);
case "get-status": response.message = "Playing track";
response.data = { break;
isAuthorized: true case "get-status":
}; response.data = {
response.message = "Status"; isAuthorized: true,
break; };
case "get-currentmediaitem": response.message = "Status";
this._win.webContents.executeJavaScript(`wsapi.getPlaybackState()`); break;
break; case "get-currentmediaitem":
case "library-status": this._win.webContents.executeJavaScript(`wsapi.getPlaybackState()`);
this._win.webContents.executeJavaScript(`wsapi.getLibraryStatus("${data.type}", "${data.id}")`); break;
break; case "library-status":
case "rating": this._win.webContents.executeJavaScript(`wsapi.getLibraryStatus("${data.type}", "${data.id}")`);
this._win.webContents.executeJavaScript(`wsapi.rate("${data.type}", "${data.id}", ${data.rating})`); break;
break; case "rating":
case "change-library": this._win.webContents.executeJavaScript(`wsapi.rate("${data.type}", "${data.id}", ${data.rating})`);
this._win.webContents.executeJavaScript(`wsapi.changeLibrary("${data.type}", "${data.id}", ${data.add})`); break;
break; case "change-library":
case "quit": this._win.webContents.executeJavaScript(`wsapi.changeLibrary("${data.type}", "${data.id}", ${data.add})`);
electron.app.quit(); break;
break; case "quit":
} electron.app.quit();
ws.send(JSON.stringify(response)); break;
});
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"
} }
this.clients.forEach(function each(client: any) { ws.send(JSON.stringify(response));
client.send(JSON.stringify(response)); });
});
}
returnRatingStatus(kind: string, id: string, rating: number) { ws.on("close", () => {
const response: standardResponse = { // remove client from list
status: 0, data: { kind, id, rating }, this.clients.splice(wsapi.clients.indexOf(ws), 1);
message: "OK", type: "rate" console.log(`Client ${ws.id} disconnected`);
}; });
this.clients.forEach(function each(client: any) { ws.send(JSON.stringify(defaultResponse));
client.send(JSON.stringify(response)); });
}); }
}
returnLibraryChange(kind: string, id: string, shouldAdd: boolean) { sendToClient(_id: any) {
const response: standardResponse = { // replace the clients.forEach with a filter to find the client that requested
status: 0, data: { kind, id, add: shouldAdd }, }
message: "OK", type: "change-library"
}; updatePlaybackState(attr: any) {
this.clients.forEach(function each(client: any) { const response: standardResponse = {
client.send(JSON.stringify(response)); 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));
});
}
}

View file

@ -1,27 +1,27 @@
require("v8-compile-cache"); require("v8-compile-cache");
import {join} from "path"; import { join } from "path";
import {app} from "electron" import { app } from "electron";
if (!app.isPackaged) { 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 { Store } from "./base/store";
import {AppEvents} from "./base/app"; import { AppEvents } from "./base/app";
import {Plugins} from "./base/plugins"; import { Plugins } from "./base/plugins";
import {BrowserWindow} from "./base/browserwindow"; import { BrowserWindow } from "./base/browserwindow";
import {init as Sentry} from "@sentry/electron"; import { init as Sentry } from "@sentry/electron";
import {RewriteFrames} from "@sentry/integrations"; import { RewriteFrames } from "@sentry/integrations";
import {components, ipcMain} from "electron" import { components, ipcMain } from "electron";
// Analytics for debugging fun yeah. // Analytics for debugging fun yeah.
Sentry({ Sentry({
dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214", dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214",
integrations: [ integrations: [
new RewriteFrames({ new RewriteFrames({
root: process.cwd(), root: process.cwd(),
}), }),
], ],
}); });
new Store(); new Store();
@ -33,31 +33,30 @@ const CiderPlug = new Plugins();
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
app.on("ready", () => { app.on("ready", () => {
Cider.ready(CiderPlug); Cider.ready(CiderPlug);
console.log("[Cider] Application is Ready. Creating Window.") console.log("[Cider] Application is Ready. Creating Window.");
if (!app.isPackaged) { if (!app.isPackaged) {
console.info("[Cider] Running in development mode.") console.info("[Cider] Running in development mode.");
require("vue-devtools").install() require("vue-devtools").install();
} }
components.whenReady().then(async () => { components.whenReady().then(async () => {
const bw = new BrowserWindow() const bw = new BrowserWindow();
const win = await bw.createWindow() const win = await bw.createWindow();
app.getGPUInfo("complete").then(gpuInfo => { app.getGPUInfo("complete").then((gpuInfo) => {
console.log(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();
});
}); });
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) => { ipcMain.handle("renderer-ready", (event) => {
CiderPlug.callPlugins("onRendererReady", event); CiderPlug.callPlugins("onRendererReady", event);
}) });
ipcMain.on("playbackStateDidChange", (_event, attributes) => { ipcMain.on("playbackStateDidChange", (_event, attributes) => {
CiderPlug.callPlugins("onPlaybackStateDidChange", attributes); CiderPlug.callPlugins("onPlaybackStateDidChange", attributes);
}); });
ipcMain.on("nowPlayingItemDidChange", (_event, attributes) => { ipcMain.on("nowPlayingItemDidChange", (_event, attributes) => {
CiderPlug.callPlugins("onNowPlayingItemDidChange", attributes); CiderPlug.callPlugins("onNowPlayingItemDidChange", attributes);
}); });
app.on("before-quit", () => { app.on("before-quit", () => {
CiderPlug.callPlugins("onBeforeQuit"); CiderPlug.callPlugins("onBeforeQuit");
console.warn(`${app.getName()} exited.`); console.warn(`${app.getName()} exited.`);
}); });
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -87,20 +86,20 @@ app.on("before-quit", () => {
// @ts-ignore // @ts-ignore
app.on("widevine-ready", (version, lastVersion) => { app.on("widevine-ready", (version, lastVersion) => {
if (null !== lastVersion) { if (null !== lastVersion) {
console.log("[Cider][Widevine] Widevine " + version + ", upgraded from " + lastVersion + ", is ready to be used!") console.log("[Cider][Widevine] Widevine " + version + ", upgraded from " + lastVersion + ", is ready to be used!");
} else { } else {
console.log("[Cider][Widevine] Widevine " + version + " is ready to be used!") console.log("[Cider][Widevine] Widevine " + version + " is ready to be used!");
} }
}) });
// @ts-ignore // @ts-ignore
app.on("widevine-update-pending", (currentVersion, pendingVersion) => { 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 // @ts-ignore
app.on("widevine-error", (error) => { app.on("widevine-error", (error) => {
console.log("[Cider][Widevine] Widevine installation encountered an error: " + error) console.log("[Cider][Widevine] Widevine installation encountered an error: " + error);
app.exit() app.exit();
}) });

View file

@ -1,372 +1,352 @@
import * as electron from 'electron'; import * as electron from "electron";
import * as os from 'os'; import * as os from "os";
import {resolve} from 'path'; import { resolve } from "path";
import * as CiderReceiver from '../base/castreceiver'; import * as CiderReceiver from "../base/castreceiver";
export default class ChromecastPlugin { 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 devices: any = [];
* Private variables for interaction in plugins private castDevices: any = [];
*/
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 GCRunning = false;
private castDevices: any = []; // 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 searchForGCDevices() {
// private GCBuffer: any; try {
// private expectedConnections = 0; let browser = this.mdns.createBrowser(this.mdns.tcp("googlecast"));
// private currentConnections = 0; browser.on("ready", browser.discover);
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;
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 { 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')) { * Runs on song change
let a = service.txt.filter((u: any) => String(u).startsWith('fn=')) * @param attributes Music Attributes
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'); onNowPlayingItemDidChange(attributes: any): void {}
}
});
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);
}
}); onRendererReady(): void {
this._win.webContents.executeJavaScript(`ipcRenderer.sendSync('get-port')`).then((result: any) => {
function getLocation(headers: any) { this.ciderPort = result;
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;
});
}
}

View file

@ -1,304 +1,299 @@
import {AutoClient} from 'discord-auto-rpc' import { AutoClient } from "discord-auto-rpc";
import {ipcMain} from "electron"; import { ipcMain } from "electron";
import fetch from 'electron-fetch' import fetch from "electron-fetch";
export default class DiscordRPC { 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) * Private variables for interaction in plugins
*/ */
public name: string = 'Discord Rich Presence'; private _utils: any;
public description: string = 'Discord RPC plugin for Cider'; private _attributes: any;
public version: string = '1.1.0'; private ready: boolean = false;
public author: string = 'vapormusic/Core (Cider Collective)';
/** /**
* Private variables for interaction in plugins * Plugin Initialization
*/ */
private _utils: any; private _client: any = null;
private _attributes: any; private _activityCache: any = {
private ready: boolean = false; details: "",
state: "",
largeImageKey: "",
largeImageText: "",
smallImageKey: "",
smallImageText: "",
instance: false,
};
/** /*******************************************************************************************
* Plugin Initialization * Public Methods
*/ * ****************************************************************************************/
private _client: any = null;
private _activityCache: any = { /**
details: '', * Runs on plugin load (Currently run on application start)
state: '', */
largeImageKey: '', constructor(utils: any) {
largeImageText: '', this._utils = utils;
smallImageKey: '', console.debug(`[Plugin][${this.name}] Loading Complete.`);
smallImageText: '', }
instance: false
/**
* 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
}; };
/******************************************************************************************* // Filter the activity
* Public Methods activity = this.filterActivity(activity, attributes);
* ****************************************************************************************/
/** if (!this.ready) {
* Runs on plugin load (Currently run on application start) this._activityCache = activity;
*/ return;
constructor(utils: any) { }
this._utils = utils;
console.debug(`[Plugin][${this.name}] Loading Complete.`); // 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 rpcVars: any = {
const self = this artist: attributes.artistName,
this.connect(); composer: attributes.composerName,
console.debug(`[Plugin][${this.name}] Ready.`); title: attributes.name,
ipcMain.on('updateRPCImage', async (_event, imageurl) => { album: attributes.albumName,
if (!this._utils.getStoreValue("general.privateEnabled")) { trackNumber: attributes.trackNumber,
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', // Replace the variables
body: postbody, Object.keys(rpcVars).forEach((key) => {
headers: { if (activity.details.includes(`{${key}}`)) {
'Content-Type': 'application/json', activity.details = activity.details.replace(`{${key}}`, rpcVars[key]);
'User-Agent': _win.webContents.getUserAgent() }
}, if (activity.state.includes(`{${key}}`)) {
}) activity.state = activity.state.replace(`{${key}}`, rpcVars[key]);
.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', // Checks if the details is greater than 128 because some songs can be that long
body: postbody, if (activity.details && activity.details.length >= 128) {
headers: { activity.details = activity.details.substring(0, 125) + "...";
'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 state is greater than 128 because some songs can be that long
/** if (activity.state && activity.state.length >= 128) {
* Runs on app stop activity.state = activity.state.substring(0, 125) + "...";
*/
onBeforeQuit(): void {
console.debug(`[Plugin][${this.name}] Stopped.`);
} }
/** // Checks if the state is greater than 128 because some songs can be that long
* Runs on playback State Change if (activity.largeImageText && activity.largeImageText.length >= 128) {
* @param attributes Music Attributes (attributes.status = current state) activity.largeImageText = activity.largeImageText.substring(0, 125) + "...";
*/
onPlaybackStateDidChange(attributes: object): void {
this._attributes = attributes
this.setActivity(attributes)
} }
/** // Check large image
* Runs on song change if (activity.largeImageKey == null || activity.largeImageKey === "" || activity.largeImageKey.length > 256) {
* @param attributes Music Attributes activity.largeImageKey = "cider";
*/
onNowPlayingItemDidChange(attributes: object): void {
this._attributes = attributes
this.setActivity(attributes)
} }
// Timestamp
/******************************************************************************************* if (new Date(attributes.endTime).getTime() < 0) {
* Private Methods delete activity.startTime;
* ****************************************************************************************/ delete activity.endTime;
/**
* 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}`));
} }
/** // not sure
* Sets the activity if (!attributes.artistName) {
* @param attributes Music Attributes delete activity.state;
*/
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;
} }
/** if (!activity.largeImageText || activity.largeImageText.length < 2) {
* Filter the Discord activity object delete activity.largeImageText;
*/
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
} }
return activity;
}
} }

View file

@ -1,236 +1,245 @@
export default class lastfm { export default class lastfm {
/**
* Base Plugin Information
*/
public name: string = "LastFM Plugin";
public version: string = "2.0.0";
public author: string = "Core (Cider Collective)";
/** private _apiCredentials = {
* Base Plugin Information key: "f9986d12aab5a0fe66193c559435ede3",
*/ secret: "acba3c29bd5973efa38cc2f0b63cc625",
public name: string = 'LastFM Plugin'; };
public version: string = '2.0.0'; /**
public author: string = 'Core (Cider Collective)'; * 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 = { constructor(utils: any) {
key: "f9986d12aab5a0fe66193c559435ede3", this._utils = utils;
secret: "acba3c29bd5973efa38cc2f0b63cc625" }
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.getWindow().webContents.executeJavaScript(`app.notyf.error("${err.message}");`);
this._utils = utils; 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 if (!attributes.lfmAlbum) {
this._utils.getIPCMain().handle('lastfm:url', (event: any) => { this._lfm.album.getInfo(
console.debug(`[${lastfm.name}:url] Called.`) {
return this._lfm.getAuthenticationUrl({"cb": "cider://auth/lastfm"}) artist: attributes.primaryArtist,
}) album: attributes.albumName,
},
this._utils.getIPCMain().on('lastfm:auth', (event: any, token: string) => { (err: any, data: any) => {
console.debug(`[${lastfm.name}:auth] Token: `, token) if (err) {
this.authenticateLastFM(token) console.error(`[${lastfm.name}] [album.getInfo] Error: ${typeof err === "string" ? err : err.message}`);
}) return {};
}
this._utils.getIPCMain().on('lastfm:disconnect', (_event: any) => { if (data) {
this._lfm.setSessionCredentials(null, null); attributes.lfmAlbum = data;
this._authenticated = false; callback(attributes);
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)
} }
);
} 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;
} }
/** if (
* Authenticate the user with the given token !this._authenticated ||
* @param token !attributes ||
* @private this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] ||
*/ (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._scrobbleCache.track === attributes.lfmTrack.name)
private authenticateLastFM(token: string): void { )
if (!token) return; return;
this._lfm.authenticate(token, (err: any, session: any) => {
if (err) {
console.error(`[${lastfm.name}:authenticate] Error: ${typeof err === "string" ? err : err.message}`);
this._utils.getWindow().webContents.executeJavaScript(`app.notyf.error("${err.message}");`) // Scrobble
return; const scrobble = {
} artist: attributes.lfmTrack.artist.name,
this._utils.getWindow().webContents.send('lastfm:authenticated', session) track: attributes.lfmTrack.name,
this._authenticated = true; album: attributes.lfmAlbum.name,
console.debug(`[${lastfm.name}:authenticate] Authenticated as ${session.username}`) 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 (
* Verifies the track information with lastfm !this._authenticated ||
* @param attributes !attributes ||
* @param callback this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] ||
* @private (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._nowPlayingCache.track === attributes.lfmTrack.name)
*/ )
private verifyTrack(attributes: any, callback: Function): void { return;
if (!attributes) return attributes;
if (!attributes.lfmAlbum) { const nowPlaying = {
this._lfm.album.getInfo({ artist: attributes.lfmTrack.artist.name,
"artist": attributes.primaryArtist, track: attributes.lfmTrack.name,
"album": attributes.albumName album: attributes.lfmAlbum.name,
}, (err: any, data: any) => { trackNumber: attributes.trackNumber,
if (err) { duration: attributes.durationInMillis / 1000,
console.error(`[${lastfm.name}] [album.getInfo] Error: ${typeof err === "string" ? err : err.message}`) albumArtist: attributes.lfmAlbum.artist,
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)
}
})
}
this._lfm.track.updateNowPlaying(nowPlaying, (err: any, res: any) => {
} if (err) {
console.error(`[${lastfm.name}:updateNowPlaying] Now Playing Update failed: ${err.message}`);
/** } else {
* Scrobbles the track to lastfm console.debug(`[${lastfm.name}:updateNowPlaying] Now Playing Updated: ${nowPlaying.artist} - ${nowPlaying.track}`);
* @param attributes this._nowPlayingCache = nowPlaying;
* @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
}
});
}
}

View file

@ -1,355 +1,338 @@
import {app, Menu, shell} from "electron"; import { app, Menu, shell } from "electron";
import {utils} from "../base/utils"; import { utils } from "../base/utils";
export default class Thumbar { 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
*/
/** private isNotMac: boolean = process.platform !== "darwin";
* Base Plugin Details (Eventually implemented into a GUI in settings) private isMac: boolean = process.platform === "darwin";
*/ private _menuTemplate: any = [
public name: string = 'Menubar Plugin'; {
public description: string = 'Creates the menubar'; label: app.getName(),
public version: string = '1.0.0'; submenu: [
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 = [
{ {
label: app.getName(), label: `${utils.getLocale(utils.getStoreValue("general.language"), "term.about")} ${app.getName()}`,
submenu: [ click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('about')`),
{ },
label: `${utils.getLocale(utils.getStoreValue('general.language'), 'term.about')} ${app.getName()}`, { type: "separator" },
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('about')`) {
}, label: utils.getLocale(utils.getStoreValue("general.language"), "term.toggleprivate"),
{type: 'separator'}, 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.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'), 'menubar.options.view'), label: utils.getLocale(utils.getStoreValue("general.language"), "term.settings"),
submenu: [ accelerator: utils.getStoreValue("general.keybindings.settings").join("+"),
...(this.isMac ? [ click: () => utils.getWindow().webContents.executeJavaScript(`app.openSettingsPage()`),
{role: 'reload'}, },
{role: 'forceReload'}, ...(this.isMac ? [{ type: "separator" }, { role: "services" }, { type: "separator" }, { role: "hide" }, { role: "hideOthers" }, { role: "unhide" }, { type: "separator" }, { role: "quit" }] : []),
{role: 'toggleDevTools'}, ...(this.isNotMac
{type: 'separator'}, ? [
{role: 'resetZoom'}, { type: "separator" },
{role: 'zoomIn'}, {
{role: 'zoomOut'}, label: utils.getLocale(utils.getStoreValue("general.language"), "term.quit"),
{type: 'separator'}, accelerator: "Control+Q",
{role: 'togglefullscreen'}, click: () => app.quit(),
{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"), "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'), label: utils.getLocale(utils.getStoreValue("general.language"), "term.browse"),
submenu: [ accelerator: utils.getStoreValue("general.keybindings.browse").join("+"),
{role: 'minimize', label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.minimize')}, click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('browse')`),
{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'
},
] : []),
],
}, },
{ type: "separator" },
{ {
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.controls'), label: utils.getLocale(utils.getStoreValue("general.language"), "term.recentlyAdded"),
submenu: [ accelerator: utils.getStoreValue("general.keybindings.recentAdd").join("+"),
{ click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-recentlyadded')`),
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'), label: utils.getLocale(utils.getStoreValue("general.language"), "term.songs"),
submenu: [ accelerator: utils.getStoreValue("general.keybindings.songs").join("+"),
{ click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-songs')`),
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'), label: utils.getLocale(utils.getStoreValue("general.language"), "term.albums"),
role: 'help', accelerator: utils.getStoreValue("general.keybindings.albums").join("+"),
submenu: [ click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('library-albums')`),
{ },
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.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.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.window"),
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.report'), submenu: [
submenu: [ {
{ role: "minimize",
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.bug'), label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.minimize"),
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) },
}, { type: "separator" },
{ ...(this.isMac
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: "Show",
{ click: () => utils.getWindow().show(),
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) { role: "zoom" },
}, { type: "separator" },
] { role: "front" },
}, { role: "close" },
{type: 'separator'}, {
{ label: "Edit",
label: utils.getLocale(utils.getStoreValue('general.language'), 'menubar.options.license'), submenu: [{ role: "undo" }, { role: "redo" }, { type: "separator" }, { role: "cut" }, { role: "copy" }, { role: "paste" }],
click: () => shell.openExternal("https://github.com/ciderapp/Cider/blob/main/LICENSE").catch(console.error) },
}, { type: "separator" },
{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()
}
] ]
} : []),
]; ...(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) * Public Methods
*/ * ****************************************************************************************/
constructor(_utils: utils) {
console.debug(`[Plugin][${this.name}] Loading Complete.`);
}
/** /**
* Runs on app ready * Runs on plugin load (Currently run on application start)
*/ */
onReady(_win: Electron.BrowserWindow): void { constructor(_utils: utils) {
const menu = Menu.buildFromTemplate(this._menuTemplate); console.debug(`[Plugin][${this.name}] Loading Complete.`);
Menu.setApplicationMenu(menu) }
}
/** /**
* Runs on app stop * Runs on app ready
*/ */
onBeforeQuit(): void { onReady(_win: Electron.BrowserWindow): void {
console.debug(`[Plugin][${this.name}] Stopped.`); const menu = Menu.buildFromTemplate(this._menuTemplate);
} Menu.setApplicationMenu(menu);
}
/** /**
* Runs on playback State Change * Runs on app stop
* @param attributes Music Attributes (attributes.status = current state) */
*/ onBeforeQuit(): void {
onPlaybackStateDidChange(attributes: object): void { console.debug(`[Plugin][${this.name}] Stopped.`);
}
} /**
* Runs on playback State Change
/** * @param attributes Music Attributes (attributes.status = current state)
* Runs on song change */
* @param attributes Music Attributes onPlaybackStateDidChange(attributes: object): void {}
*/
onNowPlayingItemDidChange(attributes: object): void {
}
/**
* Runs on song change
* @param attributes Music Attributes
*/
onNowPlayingItemDidChange(attributes: object): void {}
} }

View file

@ -1,177 +1,176 @@
// @ts-ignore // @ts-ignore
import * as Player from 'mpris-service'; import * as Player from "mpris-service";
export default class mpris { export default class mpris {
/** /**
* Private variables for interaction in plugins * Private variables for interaction in plugins
*/ */
private static utils: any; private static utils: any;
/** /**
* MPRIS Service * MPRIS Service
*/ */
private static player: Player.Player; private static player: Player.Player;
/** /**
* Base Plugin Details (Eventually implemented into a GUI in settings) * Base Plugin Details (Eventually implemented into a GUI in settings)
*/ */
public name: string = 'MPRIS Service'; public name: string = "MPRIS Service";
public description: string = 'Handles MPRIS service calls for Linux systems.'; public description: string = "Handles MPRIS service calls for Linux systems.";
public version: string = '1.0.0'; public version: string = "1.0.0";
public author: string = 'Core'; public author: string = "Core";
/******************************************************************************************* /*******************************************************************************************
* Private Methods * Private Methods
* ****************************************************************************************/ * ****************************************************************************************/
/** /**
* Runs on plugin load (Currently run on application start) * Runs on plugin load (Currently run on application start)
*/ */
constructor(utils: any) { constructor(utils: any) {
mpris.utils = utils 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 * Connects to MPRIS Service
* @private */
* @decorator private static connect() {
*/ const player = Player({
private static linuxOnly(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) { name: "cider",
if (process.platform !== 'linux') { identity: "Cider",
descriptor.value = function () { supportedInterfaces: ["player"],
return });
}
} 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 * Runs on app ready
*/ */
private static connect() { @mpris.linuxOnly
onReady(_: any): void {
console.debug(`[${mpris.name}:onReady] Ready.`);
}
const player = Player({ /**
name: 'cider', * Renderer ready
identity: 'Cider', */
supportedInterfaces: ['player'] @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; } = { * Runs on playback State Change
'none': 0, * @param attributes Music Attributes (attributes.status = current state)
'track': 1, */
'playlist': 2, @mpris.linuxOnly
} onPlaybackStateDidChange(attributes: any): void {
mpris.player.playbackStatus = attributes?.status ? Player.PLAYBACK_STATUS_PLAYING : Player.PLAYBACK_STATUS_PAUSED;
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 song change
* @param attributes Music Attributes
*/
@mpris.linuxOnly
onNowPlayingItemDidChange(attributes: object): void {
mpris.updateMetaData(attributes);
}
} }

View file

@ -1,124 +1,118 @@
import fetch from "electron-fetch"; import fetch from "electron-fetch";
import {app, nativeImage, Notification} from "electron"; import { app, nativeImage, Notification } from "electron";
import NativeImage = Electron.NativeImage; import NativeImage = Electron.NativeImage;
import {createWriteStream} from "fs"; import { createWriteStream } from "fs";
import {join} from "path"; import { join } from "path";
export default class playbackNotifications { 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) * Creates playback notification
*/ * @param a: Music Attributes
public name: string = 'Playback Notifications'; */
public description: string = 'Creates notifications on playback.'; createNotification(a: any): void {
public version: string = '1.0.0'; if (this._notification) {
public author: string = 'Core'; this._notification.close();
public contributors: string[] = ['Core', 'Monochromish']; }
private _utils: any; this._notification = new Notification({
private _notification: Notification | undefined; title: a.name,
private _artworkImage: { [key: string]: NativeImage } = {}; body: `${a.artistName}${a.albumName}`,
private _artworkNums: Array<string> = []; silent: true,
icon: this._artworkImage[a.artwork.url],
/** urgency: "low",
* Creates playback notification actions: [
* @param a: Music Attributes {
*/ type: "button",
createNotification(a: any): void { text: `${this._utils.getLocale(this._utils.getStoreValue("general.language"), "term.skip")}`,
if (this._notification) { },
this._notification.close(); ],
} 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> <toast>
<audio silent="true" /> <audio silent="true" />
<visual> <visual>
<binding template="ToastImageAndText02"> <binding template="ToastImageAndText02">
<image id="1" src="${join(app.getPath("temp"), `${a.songId}-${a.artwork.url.split('/').pop()}`)}" name="Image" /> <image id="1" src="${join(app.getPath("temp"), `${a.songId}-${a.artwork.url.split("/").pop()}`)}" name="Image" />
<text id="1">${a?.name.replace(/&/g, '&amp;')}</text> <text id="1">${a?.name.replace(/&/g, "&amp;")}</text>
<text id="2">${a?.artistName.replace(/&/g, '&amp;')} ${a?.albumName.replace(/&/g, '&amp;')}</text> <text id="2">${a?.artistName.replace(/&/g, "&amp;")} ${a?.albumName.replace(/&/g, "&amp;")}</text>
</binding> </binding>
</visual> </visual>
<actions> <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.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.next")}" activationType="protocol" arguments="cider://nextitem/"/>
</actions> </actions>
</toast>` </toast>`,
}); });
console.log(this._notification.toastXml); console.log(this._notification.toastXml);
this._notification.on('click', (_: any) => { this._notification.on("click", (_: any) => {
this._utils.getWindow().show() this._utils.getWindow().show();
this._utils.getWindow().focus() this._utils.getWindow().focus();
}) });
this._notification.on('close', (_: any) => { this._notification.on("close", (_: any) => {
this._notification = undefined; this._notification = undefined;
}) });
this._notification.on('action', (event: any, action: any) => { this._notification.on("action", (event: any, action: any) => {
this._utils.playback.next() 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.`);
/******************************************************************************************* utils.getIPCMain().on("playbackNotifications:create", (event: any, a: any) => {
* Public Methods a.artwork.url = a.artwork.url.replace("/{w}x{h}bb", "/512x512bb").replace("/2000x2000bb", "/35x35bb");
* ****************************************************************************************/
/** if (this._artworkNums.length > 20) {
* Runs on plugin load (Currently run on application start) delete this._artworkImage[this._artworkNums[0]];
*/ this._artworkNums.shift();
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._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);
});
}
}
});
}
} }

View file

@ -1,42 +1,39 @@
import * as electron from 'electron'; import * as electron from "electron";
import * as os from 'os'; import * as os from "os";
import * as fs from 'fs'; import * as fs from "fs";
import { join, resolve } from 'path'; import { join, resolve } from "path";
import * as CiderReceiver from '../base/castreceiver'; import * as CiderReceiver from "../base/castreceiver";
import fetch from 'electron-fetch'; import fetch from "electron-fetch";
import {Stream} from "stream"; import { Stream } from "stream";
import {spawn} from 'child_process'; import { spawn } from "child_process";
import {Worker} from 'worker_threads'; import { Worker } from "worker_threads";
import { Blob } from 'buffer'; import { Blob } from "buffer";
export default class RAOP { 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 airtunes: any;
* Private variables for interaction in plugins private device: any;
*/ private mdns = require("mdns-js");
private _utils: any; private ok: any = 1;
private _win: any; private devices: any = [];
private _app: any; private castDevices: any = [];
private _store: any; private i: any = false;
private _cacheAttr: any; private audioStream: any = new Stream.PassThrough();
private u: any; private ffmpeg: any = null;
private ipairplay: any = ""; private worker: any = null;
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 processNode = ` private processNode = `
import {parentPort, workerData} from "worker_threads"; import {parentPort, workerData} from "worker_threads";
function getAudioConv (buffers) { 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) { 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 )})) // 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) { if (
this.castDevices.push({ this.castDevices.findIndex((item: any) => {
name: (host ?? "Unknown").replace(".local",""), return item != null && item.name == (host ?? "Unknown").replace(".local", "") && item.port == port;
host: addresses ? addresses[0] : '', }) == -1
port: port, ) {
addresses: addresses, this.castDevices.push({
txt: text, name: (host ?? "Unknown").replace(".local", ""),
airplay2: airplay2 host: addresses ? addresses[0] : "",
}); port: port,
if (this.devices.indexOf(host) === -1) { addresses: addresses,
this.devices.push(host); txt: text,
} airplay2: airplay2,
if (name) { });
this._win.webContents.executeJavaScript(`console.log('deviceFound','ip: ${host} name:${name}')`).catch((err: any) => console.error(err)); if (this.devices.indexOf(host) === -1) {
console.log("deviceFound", host, name); this.devices.push(host);
} }
} else { if (name) {
this._win.webContents.executeJavaScript(`console.log('deviceFound (added)','ip: ${host} name:${name}')`).catch((err: any) => console.error(err)); this._win.webContents.executeJavaScript(`console.log('deviceFound','ip: ${host} name:${name}')`).catch((err: any) => console.error(err));
console.log("deviceFound (added)", host, name); 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);
} }
} });
/** const browser2 = this.mdns.createBrowser(this.mdns.tcp("airplay"));
* Base Plugin Details (Eventually implemented into a GUI in settings) browser2.on("ready", browser2.discover);
*/
public name: string = 'RAOP';
public description: string = 'RAOP Plugin';
public version: string = '0.0.1';
public author: string = 'vapormusic / Cider Collective';
/** browser2.on("update", (service: any) => {
* Runs on plugin load (Currently run on application start) if (service.addresses && service.fullname && service.fullname.includes("_airplay._tcp")) {
*/ // console.log(service.txt)
constructor(utils: { getStore: () => any; getApp: () => any; }) { this._win.webContents.executeJavaScript(`console.log(
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}" "${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')); // const browser2 = this.mdns.createBrowser(this.mdns.tcp('airplay'));
browser2.on('ready', browser2.discover); // browser2.on('ready', browser2.discover);
browser2.on('update', (service: any) => { // browser2.on('update', (service: any) => {
if (service.addresses && service.fullname && (service.fullname.includes('_airplay._tcp'))) { // if (service.addresses && service.fullname && (service.fullname.includes('_raop._tcp') || service.fullname.includes('_airplay._tcp'))) {
// console.log(service.txt) // // console.log(service.txt)
this._win.webContents.executeJavaScript(`console.log( // this._win.webContents.executeJavaScript(`console.log(
"${service.name} ${service.host}:${service.port} ${service.addresses}" // "${service.name} ${service.host}:${service.port} ${service.addresses}"
)`); // )`);
this.ondeviceup(service.name, service.host, service.port, service.addresses, service.txt, true); // this.ondeviceup(service.name, service.host, service.port, service.addresses, service.txt);
} // }
}); // });
});
// const browser2 = this.mdns.createBrowser(this.mdns.tcp('airplay'));
// browser2.on('ready', browser2.discover);
// browser2.on('update', (service: any) => { electron.ipcMain.on("performAirplayPCM", (event, ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv) => {
// if (service.addresses && service.fullname && (service.fullname.includes('_raop._tcp') || service.fullname.includes('_airplay._tcp'))) { if (ipv4 != this.ipairplay || ipport != this.portairplay) {
// // console.log(service.txt) if (this.airtunes == null) {
// this._win.webContents.executeJavaScript(`console.log( this.airtunes = new this.u();
// "${service.name} ${service.host}:${service.port} ${service.addresses}" }
// )`); this.ipairplay = ipv4;
// this.ondeviceup(service.name, service.host, service.port, service.addresses, service.txt); 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);
electron.ipcMain.on("performAirplayPCM", (event, ipv4, ipport, sepassword, title, artist, album, artworkURL,txt,airplay2dv) => { if (status == "ready") {
this._win.webContents.setAudioMuted(true);
if (ipv4 != this.ipairplay || ipport != this.portairplay) { this._win.webContents.executeJavaScript(`CiderAudio.sendAudio()`).catch((err: any) => console.error(err));
if (this.airtunes == null) { this.airtunes = new this.u()} }
this.ipairplay = ipv4; if (status == "need_password") {
this.portairplay = ipport; this._win.webContents.executeJavaScript(`app.setAirPlayCodeUI()`);
this.device = this.airtunes.add(ipv4, { }
port: ipport, if (status == "pair_success") {
volume: 50, this._win.webContents.executeJavaScript(`app.sendAirPlaySuccess()`);
password: sepassword, }
txt: txt, if (status == "pair_failed") {
airplay2: airplay2dv, this._win.webContents.executeJavaScript(`app.sendAirPlayFailed()`);
debug: true }
}); if (status == "stopped") {
// console.log('lol',txt) this.airtunes.stopAll(() => {
this.device.on('status', (status: any) => { console.log("end");
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');
}); });
this.airtunes = null; this.airtunes = null;
this.device = null; this.device = null;
this.ipairplay = ''; this.ipairplay = "";
this.portairplay = ''; this.portairplay = "";
this.ok = 1; 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) => { electron.ipcMain.on("setAirPlayPasscode", (event, passcode) => {
if (this.airtunes && this.device) { if (this.device) {
console.log(this.device.key, title, artist, album); this.device.setPasscode(passcode);
this.airtunes.setTrackInfo(this.device.key, title, artist, album); }
this.uploadImageAirplay(artworkURL) });
}
});
electron.ipcMain.on('updateRPCImage', (_event, imageurl) => { electron.ipcMain.on("writeWAV", (event, leftbuffer, rightbuffer) => {
this.uploadImageAirplay(imageurl) 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) => { // // pipe data to AirTunes
try { // this.ffmpeg.stdout.pipe(this.airtunes);
if (url != null && url != '') { // this.i = true;
//console.log(join(this._app.getPath('userData'), 'temp.png'), url); } else {
fetch(url) this.worker.postMessage({ buffer: [leftbuffer, rightbuffer] });
.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'))
} }
} }
});
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"));
}
}
} }

View file

@ -1,136 +1,134 @@
import {nativeImage, nativeTheme} from "electron"; import { nativeImage, nativeTheme } from "electron";
import {utils} from "../base/utils"; import { utils } from "../base/utils";
import {join} from "path"; import { join } from "path";
export default class Thumbar { export default class Thumbar {
/** /**
* Private variables for interaction in plugins * Private variables for interaction in plugins
*/ */
private _win: any; private _win: any;
private _app: any; private _app: any;
/** /**
* Base Plugin Details (Eventually implemented into a GUI in settings) * Base Plugin Details (Eventually implemented into a GUI in settings)
*/ */
public name: string = 'Thumbnail Toolbar Plugin'; public name: string = "Thumbnail Toolbar Plugin";
public description: string = 'Creates and managed the thumbnail toolbar buttons and their events'; public description: string = "Creates and managed the thumbnail toolbar buttons and their events";
public version: string = '1.0.0'; public version: string = "1.0.0";
public author: string = 'Core'; public author: string = "Core";
/** /**
* Thumbnail Toolbar Assets * Thumbnail Toolbar Assets
*/ */
private icons: { [key: string]: Electron.NativeImage } = { private icons: { [key: string]: Electron.NativeImage } = {
pause: nativeImage.createFromPath(join(utils.getPath('resourcePath'), 'icons/thumbar', `${nativeTheme.shouldUseDarkColors ? 'light' : 'dark'}_pause.png`)), 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`)), 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`)), 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`)), 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;
} }
/******************************************************************************************* const buttons = [
* Private Methods {
* ****************************************************************************************/ 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();
},
},
];
/** if (!attributes.playParams || attributes.playParams.id === "no-id-found") {
* Blocks non-windows systems from running this plugin this._win.setThumbarButtons([]);
* @private } else {
* @decorator this._win.setThumbarButtons(buttons);
*/
private static windowsOnly(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
if (process.platform !== 'win32') {
descriptor.value = function () {
return
}
}
} }
}
/** /*******************************************************************************************
* Update the thumbnail toolbar * Public Methods
*/ * ****************************************************************************************/
private updateButtons(attributes: any) {
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 = [ /**
{ * Runs on app stop
tooltip: 'Previous', */
icon: this.icons.previous, @Thumbar.windowsOnly
click() { onBeforeQuit(): void {
utils.playback.previous() console.debug(`[Plugin][${this.name}] Stopped.`);
} }
},
{
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()
}
}
];
if (!attributes.playParams || attributes.playParams.id === 'no-id-found') { /**
this._win.setThumbarButtons([]) * Runs on playback State Change
} else { * @param attributes Music Attributes (attributes.status = current state)
this._win.setThumbarButtons(buttons); */
} @Thumbar.windowsOnly
} onPlaybackStateDidChange(attributes: object): void {
this.updateButtons(attributes);
/******************************************************************************************* }
* 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 song change
* @param attributes Music Attributes
*/
@Thumbar.windowsOnly
onNowPlayingItemDidChange(attributes: object): void {
this.updateButtons(attributes);
}
} }

View file

@ -1,4 +1,4 @@
import * as WebSocket from 'ws'; import * as WebSocket from "ws";
/** /**
* 0-pad a number. * 0-pad a number.
@ -6,7 +6,7 @@ import * as WebSocket from 'ws';
* @param {Number} length * @param {Number} length
* @returns String * @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 * 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 * @returns String
*/ */
const convertTimeToString = (timeInSeconds: number) => { const convertTimeToString = (timeInSeconds: number) => {
const timeInMinutes = Math.floor(timeInSeconds / 60); const timeInMinutes = Math.floor(timeInSeconds / 60);
if (timeInMinutes < 60) { if (timeInMinutes < 60) {
return timeInMinutes + ":" + pad(Math.floor(timeInSeconds % 60), 2); 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); return Math.floor(timeInMinutes / 60) + ":" + pad(Math.floor(timeInMinutes % 60), 2) + ":" + pad(Math.floor(timeInSeconds % 60), 2);
} };
export default class WebNowPlaying { export default class WebNowPlaying {
/** /**
* Base Plugin Details (Eventually implemented into a GUI in settings) * Base Plugin Details (Eventually implemented into a GUI in settings)
*/ */
public name: string = 'WebNowPlaying'; public name: string = "WebNowPlaying";
public description: string = 'Song info and playback control for the Rainmeter WebNowPlaying plugin.'; public description: string = "Song info and playback control for the Rainmeter WebNowPlaying plugin.";
public version: string = '1.0.1'; public version: string = "1.0.1";
public author: string = 'Zennn <me@jozen.blue>'; public author: string = "Zennn <me@jozen.blue>";
private _win: any; private _win: any;
private ws?: WebSocket; private ws?: WebSocket;
private wsapiConn?: WebSocket; private wsapiConn?: WebSocket;
private playerName: string = 'Cider'; private playerName: string = "Cider";
constructor() { constructor() {
console.debug(`[Plugin][${this.name}] Loading Complete.`); 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;
} }
}
/** private sendSongInfo(attributes: any) {
* Blocks non-windows systems from running this plugin if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
* @private
* @decorator const fields = ["STATE", "TITLE", "ARTIST", "ALBUM", "COVER", "DURATION", "POSITION", "VOLUME", "REPEAT", "SHUFFLE"];
*/ fields.forEach((field) => {
private static windowsOnly(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) { try {
if (process.platform !== 'win32') { let value: any = "";
descriptor.value = () => void 0; 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) {
private sendSongInfo(attributes: any) { if (this.ws?.readyState === WebSocket.OPEN) {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return; this.ws.send(`Error:Error updating ${field} for ${this.playerName}`);
this.ws.send(`ErrorD:${error}`);
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];
} }
const eventName = data.split(' ')[0].toLowerCase(); }
});
}
try { private fireEvent(evt: WebSocket.MessageEvent) {
switch (eventName) { if (!evt.data) return;
case 'playpause': const data = <string>evt.data;
this._win.webContents.executeJavaScript('MusicKitInterop.playPause()').catch(console.error);
break; let value: string = "";
case 'next': if (data.split(/ (.+)/).length > 1) {
this._win.webContents.executeJavaScript('MusicKitInterop.next()').catch(console.error); value = data.split(/ (.+)/)[1];
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}`);
}
}
} }
const eventName = data.split(" ")[0].toLowerCase();
/** try {
* Runs on app ready switch (eventName) {
*/ case "playpause":
@WebNowPlaying.windowsOnly this._win.webContents.executeJavaScript("MusicKitInterop.playPause()").catch(console.error);
public onReady(win: any) { break;
this._win = win; 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 = () => { * Runs on app ready
try { */
this.ws = new WebSocket('ws://127.0.0.1:8974/'); @WebNowPlaying.windowsOnly
let retry: NodeJS.Timeout; public onReady(win: any) {
this.ws.onopen = () => { this._win = win;
console.info('[WebNowPlaying] Connected to Rainmeter');
this.ws?.send(`PLAYER:${this.playerName}`);
};
this.ws.onclose = () => { // Connect to Rainmeter plugin and retry on disconnect.
clearTimeout(retry); const init = () => {
retry = setTimeout(init, 2000); try {
}; this.ws = new WebSocket("ws://127.0.0.1:8974/");
let retry: NodeJS.Timeout;
this.ws.onerror = () => { this.ws.onopen = () => {
clearTimeout(retry); console.info("[WebNowPlaying] Connected to Rainmeter");
this.ws?.close(); this.ws?.send(`PLAYER:${this.playerName}`);
};
this.ws.onmessage = this.fireEvent?.bind(this);
} catch (error) {
console.error(error);
}
}; };
init(); this.ws.onclose = () => {
clearTimeout(retry);
retry = setTimeout(init, 2000);
};
// Connect to wsapi. Only used to update progress. this.ws.onerror = () => {
try { clearTimeout(retry);
this.wsapiConn = new WebSocket('ws://127.0.0.1:26369/'); this.ws?.close();
};
this.wsapiConn.onopen = () => { this.ws.onmessage = this.fireEvent?.bind(this);
console.info('[WebNowPlaying] Connected to wsapi'); } catch (error) {
}; console.error(error);
}
};
this.wsapiConn.onmessage = (evt: WebSocket.MessageEvent) => { init();
const response = JSON.parse(<string>evt.data);
if (response.type === 'playbackStateUpdate') { // Connect to wsapi. Only used to update progress.
this.sendSongInfo(response.data); try {
} this.wsapiConn = new WebSocket("ws://127.0.0.1:26369/");
};
} catch (error) { this.wsapiConn.onopen = () => {
console.error(error); 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);
} }
/** console.debug(`[Plugin][${this.name}] Ready.`);
* 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 playback State Change * Runs on app stop
* @param attributes Music Attributes (attributes.status = current state) */
*/ @WebNowPlaying.windowsOnly
@WebNowPlaying.windowsOnly public onBeforeQuit() {
public onPlaybackStateDidChange(attributes: any) { if (this.ws) {
this.sendSongInfo(attributes); 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 * Runs on playback State Change
* @param attributes Music Attributes * @param attributes Music Attributes (attributes.status = current state)
*/ */
@WebNowPlaying.windowsOnly @WebNowPlaying.windowsOnly
public onNowPlayingItemDidChange(attributes: any) { public onPlaybackStateDidChange(attributes: any) {
this.sendSongInfo(attributes); this.sendSongInfo(attributes);
} }
/**
* Runs on song change
* @param attributes Music Attributes
*/
@WebNowPlaying.windowsOnly
public onNowPlayingItemDidChange(attributes: any) {
this.sendSongInfo(attributes);
}
} }

View file

@ -1,12 +1,12 @@
import * as PouchDB from 'pouchdb-node'; import * as PouchDB from "pouchdb-node";
import {join} from 'path'; import { join } from "path";
import {app} from "electron"; import { app } from "electron";
PouchDB.plugin(require('pouchdb-upsert')); PouchDB.plugin(require("pouchdb-upsert"));
export class ProviderDB { export class ProviderDB {
public static db: any = null public static db: any = null;
static init() { static init() {
if (ProviderDB.db == null){ if (ProviderDB.db == null) {
ProviderDB.db = new PouchDB(join(app.getPath('userData'), 'tracksdb')) ProviderDB.db = new PouchDB(join(app.getPath("userData"), "tracksdb"));
}
} }
} }
}

View file

@ -1,180 +1,197 @@
import { ProviderDB } from "./db"; import { ProviderDB } from "./db";
import * as path from 'path'; import * as path from "path";
const { readdir } = require('fs').promises; const { readdir } = require("fs").promises;
import { utils } from '../../base/utils'; import { utils } from "../../base/utils";
import * as mm from 'music-metadata'; import * as mm from "music-metadata";
import {Md5} from 'ts-md5/dist/md5'; import { Md5 } from "ts-md5/dist/md5";
import e from "express"; import e from "express";
import { EventEmitter } from 'events'; import { EventEmitter } from "events";
export class LocalFiles { export class LocalFiles {
static localSongs: any = []; static localSongs: any = [];
static localSongsArts: any = []; static localSongsArts: any = [];
public static DB = ProviderDB.db; public static DB = ProviderDB.db;
static eventEmitter = new EventEmitter(); static eventEmitter = new EventEmitter();
static getDataType(item_id : String | any){ static getDataType(item_id: String | any) {
if ((item_id ?? ('')).startsWith('ciderlocalart')) if ((item_id ?? "").startsWith("ciderlocalart")) return "artwork";
return 'artwork' else if ((item_id ?? "").startsWith("ciderlocal")) return "track";
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() { let supporttedformats = ["mp3", "aac", "webm", "flac", "m4a", "ogg", "wav", "opus"];
ProviderDB.init() let audiofiles = files.filter((f) => supporttedformats.includes(f.substring(f.lastIndexOf(".") + 1)));
let rows = (await ProviderDB.db.allDocs({include_docs: true, let metadatalist = [];
attachments: true})).rows.map((item: any)=>{return item.doc}) let metadatalistart = [];
let tracks = rows.filter((item: any) => {return this.getDataType(item._id) == "track"}) let numid = 0;
let arts = rows.filter((item: any) => {return this.getDataType(item._id) == "artwork"}) for (var audio of audiofiles) {
this.localSongs = tracks; try {
this.localSongsArts = arts; const metadata = await mm.parseFile(audio);
return tracks; let lochash = Md5.hashStr(audio) ?? numid;
} if (metadata != null) {
let form = {
static async scanLibrary() { id: "ciderlocal" + lochash,
ProviderDB.init() _id: "ciderlocal" + lochash,
let folders = utils.getStoreValue("libraryPrefs.localPaths") type: "podcast-episodes",
if (folders == null || folders.length == null || folders.length == 0) folders = [] href: audio,
let files: any[] = [] attributes: {
for (var folder of folders) { artwork: {
// get files from the Music folder width: 3000,
files = files.concat(await LocalFiles.getFiles(folder)) 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) {
let supporttedformats = ["mp3", "aac", "webm", "flac", "m4a", "ogg", "wav", "opus"] console.error("localfiles error:", e);
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);
} }
// 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(){ static async cleanUpDB() {
let folders = utils.getStoreValue("libraryPrefs.localPaths") let folders = utils.getStoreValue("libraryPrefs.localPaths");
let rows = (await ProviderDB.db.allDocs({include_docs: true, let rows = (await ProviderDB.db.allDocs({ include_docs: true, attachments: true })).rows.map((item: any) => {
attachments: true})).rows.map((item: any)=>{return item.doc}) 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}) let tracks = rows.filter((item: any) => {
for (let hash of hashs){ return (
try{ this.getDataType(item._id) == "track" &&
ProviderDB.db.get(hash).then(function (doc: any) { !folders.some((i: String) => {
return ProviderDB.db.remove(doc); return item["attributes"]["assetUrl"].startsWith("file:///" + i);
});} catch(e){} })
try{ );
ProviderDB.db.get(hash.replace('ciderlocal','ciderlocalart')).then(function (doc: any) { });
return ProviderDB.db.remove(doc); let hashs = tracks.map((i: any) => {
});} catch(e){} return i._id;
} });
} for (let hash of hashs) {
try {
static setupHandlers () { ProviderDB.db.get(hash).then(function (doc: any) {
const app = utils.getExpress() return ProviderDB.db.remove(doc);
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);
}); });
} catch (e) {}
app.get("/ciderlocalart/:songs", (req: any, res: any) => { try {
const audio = req.params.songs; ProviderDB.db.get(hash.replace("ciderlocal", "ciderlocalart")).then(function (doc: any) {
// metadata.common.picture[0].data.toString('base64') return ProviderDB.db.remove(doc);
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) {}
return app
} }
} }
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;
}
}

View file

@ -1,185 +1,182 @@
global.ipcRenderer = require('electron').ipcRenderer; global.ipcRenderer = require("electron").ipcRenderer;
console.info('Loaded Preload') console.info("Loaded Preload");
let cache = {playParams: {id: 0}, status: null, remainingTime: 0}, let cache = { playParams: { id: 0 }, status: null, remainingTime: 0 },
playbackCache = {status: null, time: Date.now()}; playbackCache = { status: null, time: Date.now() };
const MusicKitInterop = { const MusicKitInterop = {
init: function () { init: function () {
/* MusicKit.Events.playbackStateDidChange */ /* MusicKit.Events.playbackStateDidChange */
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackStateDidChange, () => { MusicKit.getInstance().addEventListener(MusicKit.Events.playbackStateDidChange, () => {
const attributes = MusicKitInterop.getAttributes() const attributes = MusicKitInterop.getAttributes();
if (MusicKitInterop.filterTrack(attributes, true, false)) { if (MusicKitInterop.filterTrack(attributes, true, false)) {
global.ipcRenderer.send('playbackStateDidChange', attributes) global.ipcRenderer.send("playbackStateDidChange", attributes);
global.ipcRenderer.send('wsapi-updatePlaybackState', attributes); global.ipcRenderer.send("wsapi-updatePlaybackState", attributes);
} }
}); });
/* MusicKit.Events.playbackProgressDidChange */ /* MusicKit.Events.playbackProgressDidChange */
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackProgressDidChange, async () => { MusicKit.getInstance().addEventListener(MusicKit.Events.playbackProgressDidChange, async () => {
const attributes = MusicKitInterop.getAttributes() const attributes = MusicKitInterop.getAttributes();
// wsapi call // wsapi call
ipcRenderer.send('wsapi-updatePlaybackState', attributes); ipcRenderer.send("wsapi-updatePlaybackState", attributes);
// lastfm call // lastfm call
if (app.mk.currentPlaybackProgress === (app.cfg.connectivity.lastfm.scrobble_after / 100)) { 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; 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); ipcRenderer.send("lastfm:scrobbleTrack", attributes);
} }
}); });
/* MusicKit.Events.playbackTimeDidChange */ /* MusicKit.Events.playbackTimeDidChange */
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackTimeDidChange, () => { MusicKit.getInstance().addEventListener(MusicKit.Events.playbackTimeDidChange, () => {
ipcRenderer.send('mpris:playbackTimeDidChange', (MusicKit.getInstance()?.currentPlaybackTime * 1000 * 1000) ?? 0); ipcRenderer.send("mpris:playbackTimeDidChange", MusicKit.getInstance()?.currentPlaybackTime * 1000 * 1000 ?? 0);
}); });
/* MusicKit.Events.nowPlayingItemDidChange */ /* MusicKit.Events.nowPlayingItemDidChange */
MusicKit.getInstance().addEventListener(MusicKit.Events.nowPlayingItemDidChange, async () => { MusicKit.getInstance().addEventListener(MusicKit.Events.nowPlayingItemDidChange, async () => {
console.debug('[cider:preload] nowPlayingItemDidChange') console.debug("[cider:preload] nowPlayingItemDidChange");
const attributes = MusicKitInterop.getAttributes() const attributes = MusicKitInterop.getAttributes();
attributes.primaryArtist = (app.cfg.connectivity.lastfm.enabled && app.cfg.connectivity.lastfm.remove_featured) ? await this.fetchPrimaryArtist(attributes.artistName) : attributes.artistName; 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)) { if (MusicKitInterop.filterTrack(attributes, false, true)) {
global.ipcRenderer.send('nowPlayingItemDidChange', attributes); global.ipcRenderer.send("nowPlayingItemDidChange", attributes);
} else if (attributes.name !== 'no-title-found' && attributes.playParams.id !== "no-id-found") { } else if (attributes.name !== "no-title-found" && attributes.playParams.id !== "no-id-found") {
global.ipcRenderer.send('lastfm:nowPlayingChange', attributes); global.ipcRenderer.send("lastfm:nowPlayingChange", attributes);
} }
if (app.cfg.general.playbackNotifications && !document.hasFocus() && attributes.artistName && attributes.artwork && attributes.name) { if (app.cfg.general.playbackNotifications && !document.hasFocus() && attributes.artistName && attributes.artwork && attributes.name) {
global.ipcRenderer.send('playbackNotifications:create', attributes); global.ipcRenderer.send("playbackNotifications:create", attributes);
} }
if (MusicKit.getInstance().nowPlayingItem) { if (MusicKit.getInstance().nowPlayingItem) {
await this.sleep(750); await this.sleep(750);
MusicKit.getInstance().playbackRate = app.cfg.audio.playbackRate; MusicKit.getInstance().playbackRate = app.cfg.audio.playbackRate;
} }
}); });
/* MusicKit.Events.authorizationStatusDidChange */ /* MusicKit.Events.authorizationStatusDidChange */
MusicKit.getInstance().addEventListener(MusicKit.Events.authorizationStatusDidChange, () => { MusicKit.getInstance().addEventListener(MusicKit.Events.authorizationStatusDidChange, () => {
global.ipcRenderer.send('authorizationStatusDidChange', MusicKit.getInstance().authorizationStatus) global.ipcRenderer.send("authorizationStatusDidChange", MusicKit.getInstance().authorizationStatus);
}); });
/* MusicKit.Events.mediaPlaybackError */ /* MusicKit.Events.mediaPlaybackError */
MusicKit.getInstance().addEventListener(MusicKit.Events.mediaPlaybackError, (e) => { MusicKit.getInstance().addEventListener(MusicKit.Events.mediaPlaybackError, (e) => {
console.warn(`[cider:preload] mediaPlaybackError] ${e}`); console.warn(`[cider:preload] mediaPlaybackError] ${e}`);
}); });
/* MusicKit.Events.shuffleModeDidChange */ /* MusicKit.Events.shuffleModeDidChange */
MusicKit.getInstance().addEventListener(MusicKit.Events.shuffleModeDidChange, () => { MusicKit.getInstance().addEventListener(MusicKit.Events.shuffleModeDidChange, () => {
global.ipcRenderer.send('shuffleModeDidChange', MusicKit.getInstance().shuffleMode) global.ipcRenderer.send("shuffleModeDidChange", MusicKit.getInstance().shuffleMode);
}); });
/* MusicKit.Events.repeatModeDidChange */ /* MusicKit.Events.repeatModeDidChange */
MusicKit.getInstance().addEventListener(MusicKit.Events.repeatModeDidChange, () => { MusicKit.getInstance().addEventListener(MusicKit.Events.repeatModeDidChange, () => {
global.ipcRenderer.send('repeatModeDidChange', MusicKit.getInstance().repeatMode) global.ipcRenderer.send("repeatModeDidChange", MusicKit.getInstance().repeatMode);
}); });
}, },
sleep(ms) { sleep(ms) {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(resolve, ms); setTimeout(resolve, ms);
}); });
}, },
async fetchPrimaryArtist(artist) { async fetchPrimaryArtist(artist) {
if (app.mk.nowPlayingItem?.relationships?.artists) { if (app.mk.nowPlayingItem?.relationships?.artists) {
const artist = await app.mk.api.artist(app.mk.nowPlayingItem.relationships.artists.data[0].id) const artist = await app.mk.api.artist(app.mk.nowPlayingItem.relationships.artists.data[0].id);
return artist.attributes.name return artist.attributes.name;
} else { } else {
return artist return artist;
} }
}, },
getAttributes: function () { getAttributes: function () {
const mk = MusicKit.getInstance() const mk = MusicKit.getInstance();
const nowPlayingItem = mk.nowPlayingItem; const nowPlayingItem = mk.nowPlayingItem;
const isPlayingExport = mk.isPlaying; const isPlayingExport = mk.isPlaying;
const remainingTimeExport = mk.currentPlaybackTimeRemaining; const remainingTimeExport = mk.currentPlaybackTimeRemaining;
const currentPlaybackProgress = mk.currentPlaybackProgress; const currentPlaybackProgress = mk.currentPlaybackProgress;
const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {}); const attributes = nowPlayingItem != null ? nowPlayingItem.attributes : {};
attributes.songId = attributes.songId ?? attributes.playParams?.catalogId ?? attributes.playParams?.id attributes.songId = attributes.songId ?? attributes.playParams?.catalogId ?? attributes.playParams?.id;
attributes.status = isPlayingExport ?? null; attributes.status = isPlayingExport ?? null;
attributes.name = attributes?.name ?? 'no-title-found'; attributes.name = attributes?.name ?? "no-title-found";
attributes.artwork = attributes?.artwork ?? {url: ''}; attributes.artwork = attributes?.artwork ?? { url: "" };
attributes.artwork.url = (attributes?.artwork?.url ?? '').replace(`{f}`, "png"); attributes.artwork.url = (attributes?.artwork?.url ?? "").replace(`{f}`, "png");
attributes.playParams = attributes?.playParams ?? {id: 'no-id-found'}; attributes.playParams = attributes?.playParams ?? { id: "no-id-found" };
attributes.playParams.id = attributes?.playParams?.id ?? 'no-id-found'; attributes.playParams.id = attributes?.playParams?.id ?? "no-id-found";
attributes.url = { attributes.url = {
cider: `https://cider.sh/link?play/s/${nowPlayingItem?._songId ?? (nowPlayingItem?.songId ?? 'no-id-found')}`, 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')}` 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') { if (attributes.playParams.id === "no-id-found") {
attributes.playParams.id = nowPlayingItem?.id ?? 'no-id-found'; attributes.playParams.id = nowPlayingItem?.id ?? "no-id-found";
} }
attributes.albumName = attributes?.albumName ?? ''; attributes.albumName = attributes?.albumName ?? "";
attributes.artistName = attributes?.artistName ?? ''; attributes.artistName = attributes?.artistName ?? "";
attributes.genreNames = attributes?.genreNames ?? []; attributes.genreNames = attributes?.genreNames ?? [];
attributes.remainingTime = remainingTimeExport attributes.remainingTime = remainingTimeExport ? remainingTimeExport * 1000 : 0;
? remainingTimeExport * 1000 attributes.durationInMillis = attributes?.durationInMillis ?? 0;
: 0; attributes.currentPlaybackTime = mk?.currentPlaybackTime ?? 0;
attributes.durationInMillis = attributes?.durationInMillis ?? 0; attributes.currentPlaybackProgress = currentPlaybackProgress ?? 0;
attributes.currentPlaybackTime = mk?.currentPlaybackTime ?? 0; attributes.startTime = Date.now();
attributes.currentPlaybackProgress = currentPlaybackProgress ?? 0; attributes.endTime = Math.round(attributes?.playParams?.id === cache.playParams.id ? Date.now() + attributes?.remainingTime : attributes?.startTime + attributes?.durationInMillis);
attributes.startTime = Date.now(); return attributes;
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) { filterTrack: function (a, playbackCheck, mediaCheck) {
if (a.name === 'no-title-found' || a.playParams.id === "no-id-found") { if (a.name === "no-title-found" || a.playParams.id === "no-id-found") {
return; return;
} else if (mediaCheck && a.playParams.id === cache.playParams.id) { } else if (mediaCheck && a.playParams.id === cache.playParams.id) {
return; return;
} else if (playbackCheck && a.status === playbackCache.status) { } else if (playbackCheck && a.status === playbackCache.status) {
return; 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 */ } else if (playbackCheck && !a.status && a.remainingTime === playbackCache.time) {
return; /* 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}; cache = a;
return true; if (playbackCheck) playbackCache = { status: a.status, time: a.remainingTime };
}, return true;
},
play: () => { play: () => {
MusicKit.getInstance().play().catch(console.error); MusicKit.getInstance().play().catch(console.error);
}, },
pause: () => { pause: () => {
MusicKit.getInstance().pause(); MusicKit.getInstance().pause();
}, },
playPause: () => { playPause: () => {
if (MusicKit.getInstance().isPlaying) { if (MusicKit.getInstance().isPlaying) {
MusicKit.getInstance().pause(); MusicKit.getInstance().pause();
} else if (MusicKit.getInstance().nowPlayingItem != null) { } else if (MusicKit.getInstance().nowPlayingItem != null) {
MusicKit.getInstance().play().catch(console.error); MusicKit.getInstance().play().catch(console.error);
} }
}, },
next: () => { next: () => {
// try { // try {
// app.prevButtonBackIndicator = false; // app.prevButtonBackIndicator = false;
// } catch (e) { } // } catch (e) { }
// if (MusicKit.getInstance().queue.nextPlayableItemIndex != -1 && MusicKit.getInstance().queue.nextPlayableItemIndex != null) // if (MusicKit.getInstance().queue.nextPlayableItemIndex != -1 && MusicKit.getInstance().queue.nextPlayableItemIndex != null)
// MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.nextPlayableItemIndex); // MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.nextPlayableItemIndex);
MusicKit.getInstance().skipToNextItem().then(r => console.debug(`[cider:preload] [next] Skipping to Next ${r}`)); MusicKit.getInstance()
}, .skipToNextItem()
.then((r) => console.debug(`[cider:preload] [next] Skipping to Next ${r}`));
},
previous: () => { previous: () => {
// if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null) // if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null)
// MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex); // MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex);
MusicKit.getInstance().skipToPreviousItem().then(r => console.debug(`[cider:preload] [previous] Skipping to Previous ${r}`)); 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;
}); });

View file

@ -1,5 +0,0 @@
{
"js": {
"beautify.ignore": "src/renderer/index.js"
}
}

View file

@ -1,154 +1,136 @@
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 100; font-weight: 100;
font-display: swap; font-display: swap;
src: url("Inter-Thin.woff2?v=3.19") format("woff2"), src: url("Inter-Thin.woff2?v=3.19") format("woff2"), url("Inter-Thin.woff?v=3.19") format("woff");
url("Inter-Thin.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: italic; font-style: italic;
font-weight: 100; font-weight: 100;
font-display: swap; font-display: swap;
src: url("Inter-ThinItalic.woff2?v=3.19") format("woff2"), src: url("Inter-ThinItalic.woff2?v=3.19") format("woff2"), url("Inter-ThinItalic.woff?v=3.19") format("woff");
url("Inter-ThinItalic.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 200; font-weight: 200;
font-display: swap; font-display: swap;
src: url("Inter-ExtraLight.woff2?v=3.19") format("woff2"), src: url("Inter-ExtraLight.woff2?v=3.19") format("woff2"), url("Inter-ExtraLight.woff?v=3.19") format("woff");
url("Inter-ExtraLight.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: italic; font-style: italic;
font-weight: 200; font-weight: 200;
font-display: swap; font-display: swap;
src: url("Inter-ExtraLightItalic.woff2?v=3.19") format("woff2"), src: url("Inter-ExtraLightItalic.woff2?v=3.19") format("woff2"), url("Inter-ExtraLightItalic.woff?v=3.19") format("woff");
url("Inter-ExtraLightItalic.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
font-display: swap; font-display: swap;
src: url("Inter-Light.woff2?v=3.19") format("woff2"), src: url("Inter-Light.woff2?v=3.19") format("woff2"), url("Inter-Light.woff?v=3.19") format("woff");
url("Inter-Light.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: italic; font-style: italic;
font-weight: 300; font-weight: 300;
font-display: swap; font-display: swap;
src: url("Inter-LightItalic.woff2?v=3.19") format("woff2"), src: url("Inter-LightItalic.woff2?v=3.19") format("woff2"), url("Inter-LightItalic.woff?v=3.19") format("woff");
url("Inter-LightItalic.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
font-display: swap; font-display: swap;
src: url("Inter-Regular.woff2?v=3.19") format("woff2"), src: url("Inter-Regular.woff2?v=3.19") format("woff2"), url("Inter-Regular.woff?v=3.19") format("woff");
url("Inter-Regular.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: italic; font-style: italic;
font-weight: 400; font-weight: 400;
font-display: swap; font-display: swap;
src: url("Inter-Italic.woff2?v=3.19") format("woff2"), src: url("Inter-Italic.woff2?v=3.19") format("woff2"), url("Inter-Italic.woff?v=3.19") format("woff");
url("Inter-Italic.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
font-display: swap; font-display: swap;
src: url("Inter-Medium.woff2?v=3.19") format("woff2"), src: url("Inter-Medium.woff2?v=3.19") format("woff2"), url("Inter-Medium.woff?v=3.19") format("woff");
url("Inter-Medium.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: italic; font-style: italic;
font-weight: 500; font-weight: 500;
font-display: swap; font-display: swap;
src: url("Inter-MediumItalic.woff2?v=3.19") format("woff2"), src: url("Inter-MediumItalic.woff2?v=3.19") format("woff2"), url("Inter-MediumItalic.woff?v=3.19") format("woff");
url("Inter-MediumItalic.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
font-display: swap; font-display: swap;
src: url("Inter-SemiBold.woff2?v=3.19") format("woff2"), src: url("Inter-SemiBold.woff2?v=3.19") format("woff2"), url("Inter-SemiBold.woff?v=3.19") format("woff");
url("Inter-SemiBold.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: italic; font-style: italic;
font-weight: 600; font-weight: 600;
font-display: swap; font-display: swap;
src: url("Inter-SemiBoldItalic.woff2?v=3.19") format("woff2"), src: url("Inter-SemiBoldItalic.woff2?v=3.19") format("woff2"), url("Inter-SemiBoldItalic.woff?v=3.19") format("woff");
url("Inter-SemiBoldItalic.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
font-display: swap; font-display: swap;
src: url("Inter-Bold.woff2?v=3.19") format("woff2"), src: url("Inter-Bold.woff2?v=3.19") format("woff2"), url("Inter-Bold.woff?v=3.19") format("woff");
url("Inter-Bold.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: italic; font-style: italic;
font-weight: 700; font-weight: 700;
font-display: swap; font-display: swap;
src: url("Inter-BoldItalic.woff2?v=3.19") format("woff2"), src: url("Inter-BoldItalic.woff2?v=3.19") format("woff2"), url("Inter-BoldItalic.woff?v=3.19") format("woff");
url("Inter-BoldItalic.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 800; font-weight: 800;
font-display: swap; font-display: swap;
src: url("Inter-ExtraBold.woff2?v=3.19") format("woff2"), src: url("Inter-ExtraBold.woff2?v=3.19") format("woff2"), url("Inter-ExtraBold.woff?v=3.19") format("woff");
url("Inter-ExtraBold.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: italic; font-style: italic;
font-weight: 800; font-weight: 800;
font-display: swap; font-display: swap;
src: url("Inter-ExtraBoldItalic.woff2?v=3.19") format("woff2"), src: url("Inter-ExtraBoldItalic.woff2?v=3.19") format("woff2"), url("Inter-ExtraBoldItalic.woff?v=3.19") format("woff");
url("Inter-ExtraBoldItalic.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 900; font-weight: 900;
font-display: swap; font-display: swap;
src: url("Inter-Black.woff2?v=3.19") format("woff2"), src: url("Inter-Black.woff2?v=3.19") format("woff2"), url("Inter-Black.woff?v=3.19") format("woff");
url("Inter-Black.woff?v=3.19") format("woff");
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: italic; font-style: italic;
font-weight: 900; font-weight: 900;
font-display: swap; font-display: swap;
src: url("Inter-BlackItalic.woff2?v=3.19") format("woff2"), src: url("Inter-BlackItalic.woff2?v=3.19") format("woff2"), url("Inter-BlackItalic.woff?v=3.19") format("woff");
url("Inter-BlackItalic.woff?v=3.19") format("woff");
} }
/* ------------------------------------------------------- /* -------------------------------------------------------
@ -161,23 +143,22 @@ Usage:
} }
*/ */
@font-face { @font-face {
font-family: 'Inter var'; font-family: "Inter var";
font-weight: 100 900; font-weight: 100 900;
font-display: swap; font-display: swap;
font-style: normal; font-style: normal;
font-named-instance: 'Regular'; font-named-instance: "Regular";
src: url("Inter-roman.var.woff2?v=3.19") format("woff2"); src: url("Inter-roman.var.woff2?v=3.19") format("woff2");
} }
@font-face { @font-face {
font-family: 'Inter var'; font-family: "Inter var";
font-weight: 100 900; font-weight: 100 900;
font-display: swap; font-display: swap;
font-style: italic; font-style: italic;
font-named-instance: 'Italic'; font-named-instance: "Italic";
src: url("Inter-italic.var.woff2?v=3.19") format("woff2"); src: url("Inter-italic.var.woff2?v=3.19") format("woff2");
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
[EXPERIMENTAL] Multi-axis, single variable font. [EXPERIMENTAL] Multi-axis, single variable font.
@ -192,7 +173,7 @@ explicitly, e.g.
*/ */
@font-face { @font-face {
font-family: 'Inter var experimental'; font-family: "Inter var experimental";
font-weight: 100 900; font-weight: 100 900;
font-display: swap; font-display: swap;
font-style: oblique 0deg 10deg; font-style: oblique 0deg 10deg;

View file

@ -8,9 +8,9 @@ http://scripts.sil.org/OFL
*/ */
@font-face { @font-face {
font-family: 'Pretendard Variable'; font-family: "Pretendard Variable";
font-weight: 45 920; font-weight: 45 920;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
src: local('Pretendard Variable'), url('./woff2/PretendardVariable.woff2') format('woff2-variations'); src: local("Pretendard Variable"), url("./woff2/PretendardVariable.woff2") format("woff2-variations");
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -57,11 +57,11 @@ Vue.component("animated-number", {
}); });
function initMusicKit() { function initMusicKit() {
if(!this.responseText) { if (!this.responseText) {
console.log("Using stored token") console.log("Using stored token");
this.responseText = JSON.stringify({ this.responseText = JSON.stringify({
token: localStorage.getItem("lastToken") token: localStorage.getItem("lastToken"),
}) });
} }
let parsedJson = JSON.parse(this.responseText); let parsedJson = JSON.parse(this.responseText);
localStorage.setItem("lastToken", parsedJson.token); localStorage.setItem("lastToken", parsedJson.token);
@ -95,10 +95,10 @@ function capiInit() {
request.addEventListener("load", initMusicKit); request.addEventListener("load", initMusicKit);
request.onreadystatechange = function (aEvt) { request.onreadystatechange = function (aEvt) {
if (request.readyState == 4 && request.status != 200) { if (request.readyState == 4 && request.status != 200) {
if(localStorage.getItem("lastToken") != null) { if (localStorage.getItem("lastToken") != null) {
initMusicKit() initMusicKit();
} else { } 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; if (showOobe()) return;
console.log("MusicKit loaded"); console.log("MusicKit loaded");
// MusicKit global is now defined // MusicKit global is now defined
capiInit() capiInit();
}); });
window.addEventListener("drmUnsupported", function () { window.addEventListener("drmUnsupported", function () {
initMusicKit(); initMusicKit();
@ -140,12 +140,7 @@ function Clone(obj) {
} }
function uuidv4() { function uuidv4() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
(
c ^
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
).toString(16)
);
} }
function xmlToJson(xml) { function xmlToJson(xml) {
@ -196,26 +191,19 @@ async function asyncForEach(array, callback) {
var checkIfScrollIsStatic = setInterval(() => { var checkIfScrollIsStatic = setInterval(() => {
try { try {
if ( if (position === document.getElementsByClassName("lyric-body")[0].scrollTop) {
position === document.getElementsByClassName("lyric-body")[0].scrollTop
) {
clearInterval(checkIfScrollIsStatic); clearInterval(checkIfScrollIsStatic);
// do something // do something
} }
position = document.getElementsByClassName("lyric-body")[0].scrollTop; position = document.getElementsByClassName("lyric-body")[0].scrollTop;
} catch (e) { } } catch (e) {}
}, 50); }, 50);
// WebGPU Console Notification // WebGPU Console Notification
async function webGPU() { async function webGPU() {
try { try {
const currentGPU = await navigator.gpu.requestAdapter(); const currentGPU = await navigator.gpu.requestAdapter();
console.log( console.log("WebGPU enabled on", currentGPU.name, "with feature ID", currentGPU.features.size);
"WebGPU enabled on",
currentGPU.name,
"with feature ID",
currentGPU.features.size
);
} catch (e) { } catch (e) {
console.log("WebGPU disabled / WebGPU initialization failed"); console.log("WebGPU disabled / WebGPU initialization failed");
} }
@ -240,9 +228,9 @@ function isJson(item) {
webGPU().then(); webGPU().then();
function showOobe() { function showOobe() {
return false return false;
if (localStorage.getItem("music.ampwebplay.media-user-token") && localStorage.getItem("seenOOBE")) { if (localStorage.getItem("music.ampwebplay.media-user-token") && localStorage.getItem("seenOOBE")) {
return false return false;
} else { } else {
function waitForApp() { function waitForApp() {
if (typeof app.init !== "undefined") { if (typeof app.init !== "undefined") {
@ -252,7 +240,7 @@ function showOobe() {
} }
} }
waitForApp(); waitForApp();
return true return true;
} }
} }
@ -266,13 +254,7 @@ document.addEventListener("DOMContentLoaded", async function () {
document.addEventListener( document.addEventListener(
"contextmenu", "contextmenu",
function (e) { function (e) {
if ( if (e.target.tagName.toLowerCase() == "textarea" || (e.target.tagName.toLowerCase() == "input" && e.target.type != "checkbox" && e.target.type != "radio" && e.target.disabled == false)) {
e.target.tagName.toLowerCase() == "textarea" ||
(e.target.tagName.toLowerCase() == "input" &&
e.target.type != "checkbox" &&
e.target.type != "radio" &&
e.target.disabled == false)
) {
e.preventDefault(); e.preventDefault();
const menuPanel = { const menuPanel = {
items: { items: {

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
@colorMixRate: 1%; @colorMixRate: 1%;
@transparencyRate: 50%; @transparencyRate: 50%;
@keyColor : #fc3c44; @keyColor: #fc3c44;
@ciderColor: #ff2654; @ciderColor: #ff2654;
@baseColor: #1e1e1e; @baseColor: #1e1e1e;
@baseColorMix: mix(@baseColor, transparent, @transparencyRate); @baseColorMix: mix(@baseColor, transparent, @transparencyRate);
@ -10,12 +10,12 @@
@appOpacity: 0.15; @appOpacity: 0.15;
:root { :root {
--baseColor: @baseColor; --baseColor: @baseColor;
--baseColorMix: @baseColorMix; --baseColorMix: @baseColorMix;
--sidebarColor: @sidebarColor; --sidebarColor: @sidebarColor;
--sidebarColorMix: @sidebarColorMix; --sidebarColorMix: @sidebarColorMix;
--ciderColor: @ciderColor; --ciderColor: @ciderColor;
--appOpacity: @appOpacity; --appOpacity: @appOpacity;
--transparencyRate: @transparencyRate; --transparencyRate: @transparencyRate;
--macOSChromeColor: rgb(14 14 14 / 32%); --macOSChromeColor: rgb(14 14 14 / 32%);
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@
zoom: 0.95; zoom: 0.95;
} }
.app-sidebar-content { .app-sidebar-content {
padding:0px; padding: 0px;
.app-sidebar-header-text { .app-sidebar-header-text {
padding: 6px 10px; padding: 6px 10px;
@ -56,4 +56,4 @@
#app-content { #app-content {
// zoom: 0.8; // zoom: 0.8;
} }
} }

View file

@ -5,36 +5,35 @@
#app.twopanel { #app.twopanel {
--chromeHeight1: 46px; --chromeHeight1: 46px;
--chromeHeight2: 90px; --chromeHeight2: 90px;
--chromeHeight : calc(var(--chromeHeight1) + var(--chromeHeight2)); --chromeHeight: calc(var(--chromeHeight1) + var(--chromeHeight2));
.modular-fs .app-drawer .lyric-footer { .modular-fs .app-drawer .lyric-footer {
bottom: var(--chromeHeight2); bottom: var(--chromeHeight2);
} }
.app-chrome { .app-chrome {
&:not(.chrome-bottom) { &:not(.chrome-bottom) {
.app-chrome--center { .app-chrome--center {
flex: 1; flex: 1;
.top-nav-group { .top-nav-group {
background : #1e1e1e99; background: #1e1e1e99;
border : 1px solid lighten(@baseColor, 8); border: 1px solid lighten(@baseColor, 8);
border-radius: 12px; border-radius: 12px;
display : flex; display: flex;
height : 32px; height: 32px;
.app-sidebar-item { .app-sidebar-item {
background-color: #1e1e1e00; background-color: #1e1e1e00;
border-radius : 10px !important; border-radius: 10px !important;
border : 0px; border: 0px;
min-width : 120px; min-width: 120px;
padding : 6px; padding: 6px;
justify-content : center; justify-content: center;
align-items : center; align-items: center;
margin : 0px; margin: 0px;
height : 100%; height: 100%;
position : relative; position: relative;
white-space: nowrap; white-space: nowrap;
._svg-icon { ._svg-icon {
@ -42,18 +41,18 @@
} }
&:before { &:before {
--dist : 1px; --dist: 1px;
content : ''; content: "";
position : absolute; position: absolute;
top : var(--dist); top: var(--dist);
left : var(--dist); left: var(--dist);
right : var(--dist); right: var(--dist);
bottom : var(--dist); bottom: var(--dist);
background-color: #fff; background-color: #fff;
opacity : 0; opacity: 0;
border-radius : 10px; border-radius: 10px;
transform : scale(0.5); transform: scale(0.5);
transition : transform 0.2s ease-in-out, opacity 0.2s ease-in-out; transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
} }
&:after { &:after {
@ -65,8 +64,8 @@
&:before { &:before {
transition: transform 0.1s ease-in-out, opacity 0.1s ease-in-out; transition: transform 0.1s ease-in-out, opacity 0.1s ease-in-out;
opacity : .1; opacity: 0.1;
transform : scale(1); transform: scale(1);
} }
} }
@ -74,15 +73,15 @@
background-color: transparent; background-color: transparent;
&:before { &:before {
opacity : .2; opacity: 0.2;
transform: scale(1); transform: scale(1);
} }
} }
&.md-btn-primary { &.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); background-color: lighten(@baseColor, @colorMixRate * 5);
z-index : 1; z-index: 1;
} }
} }
} }
@ -90,7 +89,7 @@
} }
.app-mainmenu { .app-mainmenu {
width : 30px; width: 30px;
height: 30px; height: 30px;
} }
@ -101,11 +100,10 @@
height: var(--chromeHeight1); height: var(--chromeHeight1);
&.chrome-bottom { &.chrome-bottom {
background : var(--color2); background: var(--color2);
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
height : var(--chromeHeight2); height: var(--chromeHeight2);
box-shadow : 0px -2px 6px rgb(20 20 20 / 12%), box-shadow: 0px -2px 6px rgb(20 20 20 / 12%), 0px -1px 0px 0px rgb(200 200 200 / 12%);
0px -1px 0px 0px rgb(200 200 200 / 12%);
z-index: 4; z-index: 4;
.app-chrome-playback-duration-bottom { .app-chrome-playback-duration-bottom {
@ -116,33 +114,33 @@
} }
.col-sm-auto { .col-sm-auto {
width : 4em; width: 4em;
display : flex; display: flex;
justify-content: center; justify-content: center;
align-items : center; align-items: center;
font-size : 0.8em; font-size: 0.8em;
} }
input[type=range] { input[type="range"] {
appearance : none; appearance: none;
width : 100%; width: 100%;
height : 5px; height: 5px;
background-color: rgb(200 200 200 / 10%); background-color: rgb(200 200 200 / 10%);
border-radius : 6px; border-radius: 6px;
box-shadow : 0px 0px 0px 1px rgba(0 0 0 / 10%); box-shadow: 0px 0px 0px 1px rgba(0 0 0 / 10%);
align-self : center; align-self: center;
&::-webkit-slider-thumb { &::-webkit-slider-thumb {
opacity : 0; opacity: 0;
transform : scale(1); transform: scale(1);
-webkit-appearance: none; -webkit-appearance: none;
appearance : none; appearance: none;
width : 16px; width: 16px;
height : 16px; height: 16px;
border-radius : 100%; border-radius: 100%;
background : var(--keyColor); background: var(--keyColor);
cursor : default; cursor: default;
transition : opacity .10s var(--appleEase), transform .10s var(--appleEase); transition: opacity 0.1s var(--appleEase), transform 0.1s var(--appleEase);
} }
&:hover { &:hover {
@ -169,38 +167,37 @@
.playback-button.play, .playback-button.play,
.playback-button.pause, .playback-button.pause,
.playback-button.stop { .playback-button.stop {
width : 42px; width: 42px;
height : 42px; height: 42px;
border-radius: 50%; border-radius: 50%;
margin : 6px; margin: 6px;
} }
.app-chrome--center { .app-chrome--center {
display : flex; display: flex;
flex-direction: column; flex-direction: column;
.app-chrome-playback-controls { .app-chrome-playback-controls {
display : flex; display: flex;
z-index : 1; z-index: 1;
// margin-bottom: 12px; // margin-bottom: 12px;
} }
.app-chrome-playback-duration { .app-chrome-playback-duration {
position : relative; position: relative;
width : 80%; width: 80%;
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
height : 16px; height: 16px;
.song-progress { .song-progress {
@bgColor : transparent; @bgColor: transparent;
height : 16px; height: 16px;
position : absolute; position: absolute;
bottom : 4px; bottom: 4px;
left : 0px; left: 0px;
right : 4px; right: 4px;
background: @bgColor; background: @bgColor;
z-index : 0; z-index: 0;
.song-duration { .song-duration {
display: flex; display: flex;
@ -208,64 +205,63 @@
.song-duration p { .song-duration p {
font-weight: 400; font-weight: 400;
font-size : 10px; font-size: 10px;
height : 1.2em; height: 1.2em;
line-height: 1.3em; line-height: 1.3em;
overflow : hidden; overflow: hidden;
margin : 0 0 0 0.25em; margin: 0 0 0 0.25em;
} }
&:hover { &:hover {
>input[type=range] { > input[type="range"] {
&::-webkit-slider-thumb { &::-webkit-slider-thumb {
opacity : 1; opacity: 1;
transform: scale(1); transform: scale(1);
z-index : 1; z-index: 1;
} }
} }
} }
input[type=range] { input[type="range"] {
appearance : none; appearance: none;
width : 100%; width: 100%;
height : 4px; height: 4px;
background-color: rgb(200 200 200 / 10%); background-color: rgb(200 200 200 / 10%);
border-radius : 2px; border-radius: 2px;
&::-webkit-slider-thumb { &::-webkit-slider-thumb {
opacity : 0; opacity: 0;
transform : scale(0.5); transform: scale(0.5);
-webkit-appearance: none; -webkit-appearance: none;
appearance : none; appearance: none;
width : 12px; width: 12px;
height : 12px; height: 12px;
border-radius : 100%; border-radius: 100%;
background : var(--keyColor); background: var(--keyColor);
cursor : default; cursor: default;
transition : opacity .10s var(--appleEase), transform .10s var(--appleEase); transition: opacity 0.1s var(--appleEase), transform 0.1s var(--appleEase);
} }
} }
} }
} }
} }
.app-chrome--left { .app-chrome--left {
width : 30%; width: 30%;
justify-content : flex-start; justify-content: flex-start;
align-items : flex-start; align-items: flex-start;
-webkit-app-region: no-drag !important; -webkit-app-region: no-drag !important;
.playback-controls { .playback-controls {
-webkit-app-region: no-drag !important; -webkit-app-region: no-drag !important;
.artwork { .artwork {
--offset : 20px; --offset: 20px;
--marginOffset: 2; --marginOffset: 2;
--size : calc(var(--chromeHeight2) - var(--offset)); --size: calc(var(--chromeHeight2) - var(--offset));
width : var(--size); width: var(--size);
height : var(--size); height: var(--size);
margin : 0 calc(var(--offset) / var(--marginOffset)) 0 calc(var(--offset) / var(--marginOffset)); margin: 0 calc(var(--offset) / var(--marginOffset)) 0 calc(var(--offset) / var(--marginOffset));
.mediaitem-artwork, .mediaitem-artwork,
img { img {
@ -275,13 +271,13 @@
.playback-info { .playback-info {
align-items: flex-start; align-items: flex-start;
margin : 6px; margin: 6px;
.song-name { .song-name {
text-align : left; text-align: left;
font-size : 0.8em; font-size: 0.8em;
font-weight : 500; font-weight: 500;
width : 100%; width: 100%;
-webkit-mask-image: linear-gradient(-90deg, transparent 0%, transparent 10%, black 20%); -webkit-mask-image: linear-gradient(-90deg, transparent 0%, transparent 10%, black 20%);
} }
@ -306,23 +302,22 @@
.song-artist-album-content { .song-artist-album-content {
text-align: left; text-align: left;
font-size : 12px; font-size: 12px;
} }
} }
width: 100%;
width : 100%; height: 100%;
height : 100%;
max-width: 100%; max-width: 100%;
border : 0px; border: 0px;
} }
flex: 0 0 auto; flex: 0 0 auto;
} }
.app-chrome--right { .app-chrome--right {
width : 30%; width: 30%;
flex : 0 0 auto; flex: 0 0 auto;
padding-right: 8px; padding-right: 8px;
} }
} }
@ -334,7 +329,6 @@
} }
} }
// screen width is less than 768px // screen width is less than 768px
@media (max-width: 1100px) { @media (max-width: 1100px) {
#app.twopanel .app-chrome:not(.chrome-bottom) .app-chrome--center { #app.twopanel .app-chrome:not(.chrome-bottom) .app-chrome--center {

View file

@ -26,7 +26,6 @@
} }
} }
// Buttons // Buttons
.md-btn { .md-btn {
font-family: inherit; font-family: inherit;
@ -115,7 +114,6 @@
} }
} }
.md-close-btn { .md-close-btn {
-webkit-mask-image: url("ameres://icons/webui/close.svg"); -webkit-mask-image: url("ameres://icons/webui/close.svg");
-webkit-mask-repeat: no-repeat; -webkit-mask-repeat: no-repeat;
@ -165,7 +163,7 @@
.page-btn { .page-btn {
align-self: center; align-self: center;
height: 32px; height: 32px;
width:max-content; width: max-content;
} }
.page-btn img { .page-btn img {
@ -174,19 +172,19 @@
} }
.md-ico-first { .md-ico-first {
content: url('./assets/angles-left.svg'); content: url("./assets/angles-left.svg");
} }
.md-ico-prev { .md-ico-prev {
content: url('./assets/chevron-left.svg'); content: url("./assets/chevron-left.svg");
} }
.md-ico-next { .md-ico-next {
content: url('./assets/chevron-right.svg'); content: url("./assets/chevron-right.svg");
} }
.md-ico-last { .md-ico-last {
content: url('./assets/angles-right.svg'); content: url("./assets/angles-right.svg");
} }
.reload-btn { .reload-btn {
@ -234,7 +232,7 @@
&:hover { &:hover {
cursor: pointer; cursor: pointer;
background: rgb(200 200 200 / 10%) background: rgb(200 200 200 / 10%);
} }
} }
@ -315,7 +313,6 @@
} }
} }
#artworkLCD img { #artworkLCD img {
image-rendering: auto; image-rendering: auto;
} }
@ -514,7 +511,7 @@
.subtitle { .subtitle {
width: 90%; width: 90%;
font-size: .8em; font-size: 0.8em;
opacity: 0.7; opacity: 0.7;
} }
@ -570,39 +567,39 @@
*/ */
@keyframes load-bar { @keyframes load-bar {
10% { 10% {
box-shadow: inset 0 -4px 0 box-shadow: inset 0 -4px 0;
} }
20% { 20% {
box-shadow: inset 0 -10px 0 box-shadow: inset 0 -10px 0;
} }
30% { 30% {
box-shadow: inset 0 -12px 0 box-shadow: inset 0 -12px 0;
} }
40% { 40% {
box-shadow: inset 0 -8px 0 box-shadow: inset 0 -8px 0;
} }
50% { 50% {
box-shadow: inset 0 -4px 0 box-shadow: inset 0 -4px 0;
} }
60% { 60% {
box-shadow: inset 0 -6px 0 box-shadow: inset 0 -6px 0;
} }
80% { 80% {
box-shadow: inset 0 -12px 0 box-shadow: inset 0 -12px 0;
} }
90% { 90% {
box-shadow: inset 0 -6px 0 box-shadow: inset 0 -6px 0;
} }
to { to {
box-shadow: inset 0 -2px 0 box-shadow: inset 0 -2px 0;
} }
} }
@ -629,17 +626,17 @@
.loadbar-sound::before { .loadbar-sound::before {
content: ""; content: "";
position: absolute; position: absolute;
bottom: 0 bottom: 0;
} }
.loadbar-sound::before { .loadbar-sound::before {
left: -4.5px; left: -4.5px;
animation-delay: -2.4s animation-delay: -2.4s;
} }
.loadbar-sound::after { .loadbar-sound::after {
right: -4.2px; right: -4.2px;
animation-delay: -3.7s animation-delay: -3.7s;
} }
.isLibrary { .isLibrary {
@ -670,7 +667,6 @@
box-shadow: var(--mediaItemShadow); box-shadow: var(--mediaItemShadow);
} }
&:active { &:active {
background: var(--selected-click); background: var(--selected-click);
box-shadow: var(--mediaItemShadow); box-shadow: var(--mediaItemShadow);
@ -805,7 +801,6 @@
&:hover + .cd-mediaitem-square-large-overlay { &:hover + .cd-mediaitem-square-large-overlay {
opacity: 1; opacity: 1;
} }
&:hover { &:hover {
@ -813,7 +808,6 @@
} }
} }
/* mediaitem-square-large */ /* mediaitem-square-large */
.cd-mediaitem-square-large { .cd-mediaitem-square-large {
width: 190px; width: 190px;
@ -855,12 +849,10 @@
margin: 10px; margin: 10px;
margin-top: 0px; margin-top: 0px;
opacity: 0; opacity: 0;
} }
.cd-mediaitem-square-large-overlay > * { .cd-mediaitem-square-large-overlay > * {
pointer-events: auto; pointer-events: auto;
} }
.cd-mediaitem-square-large > .cd-mediaitem-square-large-overlay { .cd-mediaitem-square-large > .cd-mediaitem-square-large-overlay {
@ -873,15 +865,12 @@
.cd-mediaitem-square-large + .cd-mediaitem-square-large-overlay { .cd-mediaitem-square-large + .cd-mediaitem-square-large-overlay {
pointer-events: none; pointer-events: none;
} }
.cd-mediaitem-square-large:hover + .cd-mediaitem-square-large-overlay { .cd-mediaitem-square-large:hover + .cd-mediaitem-square-large-overlay {
opacity: 1; opacity: 1;
} }
.cd-mediaitem-square-large .artwork.round { .cd-mediaitem-square-large .artwork.round {
border-radius: var(--mediaItemRadiusRound); border-radius: var(--mediaItemRadiusRound);
} }
@ -940,12 +929,10 @@
margin: 10px; margin: 10px;
margin-top: 0px; margin-top: 0px;
opacity: 0; opacity: 0;
} }
.cd-mediaitem-mvview-overlay > * { .cd-mediaitem-mvview-overlay > * {
pointer-events: auto; pointer-events: auto;
} }
.cd-mediaitem-mvview > .cd-mediaitem-mvview-overlay { .cd-mediaitem-mvview > .cd-mediaitem-mvview-overlay {
@ -958,15 +945,12 @@
.cd-mediaitem-mvview + .cd-mediaitem-mvview-overlay { .cd-mediaitem-mvview + .cd-mediaitem-mvview-overlay {
pointer-events: none; pointer-events: none;
} }
.cd-mediaitem-mvview:hover + .cd-mediaitem-mvview-overlay { .cd-mediaitem-mvview:hover + .cd-mediaitem-mvview-overlay {
opacity: 1; opacity: 1;
} }
.cd-mediaitem-mvview .artwork.round { .cd-mediaitem-mvview .artwork.round {
border-radius: var(--mediaItemRadiusRound); border-radius: var(--mediaItemRadiusRound);
} }
@ -982,10 +966,9 @@
font-size: 12px; font-size: 12px;
} }
/* mediaitem-square */ /* mediaitem-square */
.cd-mediaitem-square { .cd-mediaitem-square {
--transitionDuration: .5s; --transitionDuration: 0.5s;
--scaleRate: 1.25; --scaleRate: 1.25;
--scaleRateArtwork: 1; --scaleRateArtwork: 1;
width: calc(160px * var(--windowRelativeScale)); width: calc(160px * var(--windowRelativeScale));
@ -1062,7 +1045,6 @@
bottom: 14px; bottom: 14px;
left: 14px; left: 14px;
z-index: 2; z-index: 2;
} }
> .menu-btn { > .menu-btn {
@ -1110,7 +1092,6 @@
// } // }
// } // }
.info-rect { .info-rect {
width: 90%; width: 90%;
height: 100%; height: 100%;
@ -1119,7 +1100,6 @@
align-items: center; align-items: center;
} }
.title { .title {
width: 100%; width: 100%;
text-align: center; text-align: center;
@ -1174,19 +1154,19 @@
--scaleRateArtwork: 1.1; --scaleRateArtwork: 1.1;
width: calc(240px * var(--scaleRate)); width: calc(240px * var(--scaleRate));
height: calc(200px * var(--scaleRate)); height: calc(200px * var(--scaleRate));
.artwork-container > .artwork { .artwork-container > .artwork {
width: calc(220px * var(--scaleRateArtwork)); width: calc(220px * var(--scaleRateArtwork));
height: calc(123px * var(--scaleRateArtwork)); height: calc(123px * var(--scaleRateArtwork));
} }
} }
@media (min-width: 1550px) { @media (min-width: 1550px) {
--scaleRate: 1.25; --scaleRate: 1.25;
--scaleRateArtwork: 1.25; --scaleRateArtwork: 1.25;
width: calc(240px * var(--scaleRate)); width: calc(240px * var(--scaleRate));
height: calc(200px * var(--scaleRate)); height: calc(200px * var(--scaleRate));
.artwork-container > .artwork { .artwork-container > .artwork {
width: calc(220px * var(--scaleRateArtwork)); width: calc(220px * var(--scaleRateArtwork));
height: calc(123px * var(--scaleRateArtwork)); height: calc(123px * var(--scaleRateArtwork));
@ -1212,19 +1192,19 @@
--scaleRateArtwork: 1.1; --scaleRateArtwork: 1.1;
width: calc(240px * var(--scaleRate)); width: calc(240px * var(--scaleRate));
height: calc(200px * var(--scaleRate)); height: calc(200px * var(--scaleRate));
.artwork-container > .artwork { .artwork-container > .artwork {
width: calc(220px * var(--scaleRateArtwork)); width: calc(220px * var(--scaleRateArtwork));
height: calc(123px * var(--scaleRateArtwork)); height: calc(123px * var(--scaleRateArtwork));
} }
} }
@media (min-width: 1550px) { @media (min-width: 1550px) {
--scaleRate: 1.25; --scaleRate: 1.25;
--scaleRateArtwork: 1.25; --scaleRateArtwork: 1.25;
width: calc(240px * var(--scaleRate)); width: calc(240px * var(--scaleRate));
height: calc(200px * var(--scaleRate)); height: calc(200px * var(--scaleRate));
.artwork-container > .artwork { .artwork-container > .artwork {
width: calc(220px * var(--scaleRateArtwork)); width: calc(220px * var(--scaleRateArtwork));
height: calc(123px * var(--scaleRateArtwork)); height: calc(123px * var(--scaleRateArtwork));
@ -1338,7 +1318,7 @@
} }
&:hover { &:hover {
.artwork{ .artwork {
filter: brightness(0.8); filter: brightness(0.8);
} }
.info-rect-card::before { .info-rect-card::before {
@ -1430,7 +1410,6 @@
} }
&:hover { &:hover {
> .play-btn, > .play-btn,
> .menu-btn { > .menu-btn {
opacity: 1; opacity: 1;
@ -1438,7 +1417,6 @@
} }
} }
.title { .title {
width: 90%; width: 90%;
text-align: center; text-align: center;
@ -1471,7 +1449,6 @@
} }
} }
.listitem-horizontal { .listitem-horizontal {
.cd-mediaitem-list-item { .cd-mediaitem-list-item {
width: 350px; width: 350px;
@ -1497,7 +1474,6 @@
&:hover::-webkit-scrollbar { &:hover::-webkit-scrollbar {
display: initial; display: initial;
} }
} }
@ -1536,9 +1512,8 @@
} }
} }
/* Switch Checkbox */ /* Switch Checkbox */
input[type=checkbox][switch] { input[type="checkbox"][switch] {
width: 38px; width: 38px;
appearance: none; appearance: none;
border-radius: 32px; border-radius: 32px;
@ -1554,12 +1529,12 @@ input[type=checkbox][switch] {
margin: 0; margin: 0;
} }
input[type=checkbox][switch]:focus, input[type="checkbox"][switch]:focus,
input[type=checkbox][switch]:active { input[type="checkbox"][switch]:active {
outline: none; outline: none;
} }
input[type=checkbox][switch]:checked { input[type="checkbox"][switch]:checked {
background: var(--keyColor); background: var(--keyColor);
border: 0 solid var(--keyColor); border: 0 solid var(--keyColor);
mix-blend-mode: unset; 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; background: white;
width: 26px; width: 26px;
height: 26px; height: 26px;
top: -1px; top: -1px;
left: -1px; left: -1px;
position: absolute; position: absolute;
content: ' '; content: " ";
border-radius: 32px; border-radius: 32px;
transition: .10s left var(--appleEase); transition: 0.1s left var(--appleEase);
transform: scale(.75); transform: scale(0.75);
} }
input[type="checkbox"][switch]:checked::before {
input[type=checkbox][switch]:checked::before {
background: white; background: white;
top: -1px; top: -1px;
left: 13px; left: 13px;
transition: .10s left var(--appleEase); transition: 0.1s left var(--appleEase);
transform: scale(.75); transform: scale(0.75);
} }
input[type=checkbox][switch]:disabled::before { input[type="checkbox"][switch]:disabled::before {
opacity: .5; opacity: 0.5;
} }
input[type=checkbox][switch]:active::before { input[type="checkbox"][switch]:active::before {
left: 13px; left: 13px;
} }
input[type=checkbox][switch]:checked:active::before { input[type="checkbox"][switch]:checked:active::before {
left: -1px; left: -1px;
} }
/* End Switch Checkbox */ /* End Switch Checkbox */
.header-text { .header-text {
margin: 0px; margin: 0px;
} }
@ -1649,7 +1622,7 @@ input[type=checkbox][switch]:checked:active::before {
.media-item--small .text { .media-item--small .text {
font-weight: 600; font-weight: 600;
font-size: 0.90em; font-size: 0.9em;
} }
.media-item--small .subtext { .media-item--small .subtext {
@ -1684,11 +1657,11 @@ input[type=checkbox][switch]:checked:active::before {
background-repeat: no-repeat; background-repeat: no-repeat;
border-radius: 8px; border-radius: 8px;
box-shadow: inset 0px 0px 0px 1px rgb(200 200 200 / 16%), 0 8px 40px rgb(0 0 0 / 0.55); 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 { .media-artwork.paused {
transition: transform .35s var(--appleEase); transition: transform 0.35s var(--appleEase);
transform: scale(0.85); transform: scale(0.85);
} }
@ -1727,7 +1700,7 @@ input[type=checkbox][switch]:checked:active::before {
background-size: 12px; background-size: 12px;
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
opacity: 0.70; opacity: 0.7;
border-radius: 6px; border-radius: 6px;
position: relative; position: relative;
@ -1745,7 +1718,7 @@ input[type=checkbox][switch]:checked:active::before {
z-index: -1; z-index: -1;
transform: scale(0.5); transform: scale(0.5);
pointer-events: none; pointer-events: none;
transition: opacity .10s var(--appleEase), transform .10s var(--appleEase); transition: opacity 0.1s var(--appleEase), transform 0.1s var(--appleEase);
} }
&:hover { &:hover {
@ -1768,7 +1741,7 @@ input[type=checkbox][switch]:checked:active::before {
height: 32px; height: 32px;
border: 0px; border: 0px;
box-shadow: unset; box-shadow: unset;
opacity: 0.70; opacity: 0.7;
position: relative; position: relative;
&:before { &:before {
@ -1785,7 +1758,7 @@ input[type=checkbox][switch]:checked:active::before {
z-index: -1; z-index: -1;
transform: scale(0.5); transform: scale(0.5);
pointer-events: none; pointer-events: none;
transition: opacity .10s var(--appleEase), transform .10s var(--appleEase); transition: opacity 0.1s var(--appleEase), transform 0.1s var(--appleEase);
} }
&:hover { &:hover {
@ -1845,7 +1818,7 @@ input[type=checkbox][switch]:checked:active::before {
} }
.playback-button.stop { .playback-button.stop {
background-image: url('./assets/cider-icons/stop.svg'); background-image: url("./assets/cider-icons/stop.svg");
background-size: 38px; background-size: 38px;
background-position: center; background-position: center;
} }
@ -1863,25 +1836,25 @@ input[type=checkbox][switch]:checked:active::before {
} }
.playback-button.pause { .playback-button.pause {
background-image: url('./assets/cider-icons/pause.svg'); background-image: url("./assets/cider-icons/pause.svg");
background-size: 38px; background-size: 38px;
background-position: center; background-position: center;
} }
.playback-button.play { .playback-button.play {
background-image: url('./assets/cider-icons/play.svg'); background-image: url("./assets/cider-icons/play.svg");
background-size: 38px; background-size: 38px;
background-position: center; background-position: center;
} }
.playback-button.next { .playback-button.next {
background-image: url('./assets/cider-icons/forward.svg'); background-image: url("./assets/cider-icons/forward.svg");
background-size: 60%; background-size: 60%;
background-position: center; background-position: center;
} }
.playback-button.previous { .playback-button.previous {
background-image: url('./assets/cider-icons/backward.svg'); background-image: url("./assets/cider-icons/backward.svg");
background-size: 60%; background-size: 60%;
background-position: center; background-position: center;
} }
@ -1950,7 +1923,7 @@ input[type=checkbox][switch]:checked:active::before {
} }
.player-song-artist { .player-song-artist {
font-size: 1.0em; font-size: 1em;
text-align: left; text-align: left;
margin: 0 auto; margin: 0 auto;
color: var(--keyColor); color: var(--keyColor);
@ -1999,10 +1972,8 @@ input[type=checkbox][switch]:checked:active::before {
height: 40px; height: 40px;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.list-entry-header { .list-entry-header {
display: flex; display: flex;
align-items: center; align-items: center;
@ -2204,9 +2175,9 @@ input[type=checkbox][switch]:checked:active::before {
.fancy-pills { .fancy-pills {
.nav-pills { .nav-pills {
position: relative; position: relative;
.nav-link { .nav-link {
transition: transform .3s var(--appleEase); transition: transform 0.3s var(--appleEase);
position: relative; position: relative;
background-color: transparent; background-color: transparent;
border: 0; border: 0;
@ -2215,8 +2186,7 @@ input[type=checkbox][switch]:checked:active::before {
-webkit-user-drag: none; -webkit-user-drag: none;
font-weight: 500; font-weight: 500;
margin: 0px 4px; margin: 0px 4px;
&:after { &:after {
--dist: 1px; --dist: 1px;
content: ""; content: "";
@ -2231,27 +2201,23 @@ input[type=checkbox][switch]:checked:active::before {
border-radius: 50px; border-radius: 50px;
z-index: -1; z-index: -1;
opacity: 0; 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 { &:hover {
outline: none; outline: none;
transform: scale(1.1); transform: scale(1.1);
// background: #eee; // background: #eee;
background: transparent; background: transparent;
color: #333; color: #333;
&:after { &:after {
opacity: 1; opacity: 1;
background-color: #eee; background-color: #eee;
transition: background-color .25s var(--appleEase), transition: background-color 0.25s var(--appleEase), border-radius 0.25s var(--appleEase), color 0s var(--appleEase), opacity 0s var(--appleEase);
border-radius .25s var(--appleEase),
color .0s var(--appleEase),
opacity 0.0s var(--appleEase);
} }
} }
&.active { &.active {
outline: none; outline: none;
transform: scale(1.1); transform: scale(1.1);
@ -2259,24 +2225,22 @@ input[type=checkbox][switch]:checked:active::before {
background: transparent; background: transparent;
color: #333; color: #333;
font-weight: 600; font-weight: 600;
&:after { &:after {
opacity: 1; opacity: 1;
background-color: #eee; background-color: #eee;
} }
} }
} }
&:hover { &:hover {
.nav-link.active { .nav-link.active {
outline: none; outline: none;
transform: scale(1.0); transform: scale(1);
background: transparent; background: transparent;
color: #eee; color: #eee;
transform: scale(1.0); transform: scale(1);
&:after { &:after {
background: rgb(200 200 200 / 15%); background: rgb(200 200 200 / 15%);
opacity: 1; opacity: 1;
@ -2284,12 +2248,12 @@ input[type=checkbox][switch]:checked:active::before {
// border-radius: 5px; // border-radius: 5px;
--dist: 4px; --dist: 4px;
} }
&:hover { &:hover {
transform: scale(1.1); transform: scale(1.1);
z-index: 1; z-index: 1;
color: #333; color: #333;
&:after { &:after {
background: #eee; background: #eee;
border-radius: inherit; border-radius: inherit;
@ -2298,9 +2262,9 @@ input[type=checkbox][switch]:checked:active::before {
} }
} }
} }
&:after { &:after {
content: ''; content: "";
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -2387,7 +2351,7 @@ input[type=checkbox][switch]:checked:active::before {
border-radius: 50%; border-radius: 50%;
div { div {
border-radius: 50%; border-radius: 50%;
} }
img { img {
border-radius: 50%; border-radius: 50%;
} }
@ -2399,8 +2363,7 @@ input[type=checkbox][switch]:checked:active::before {
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
margin-bottom: 16px; margin-bottom: 16px;
.md-input-number{ .md-input-number {
min-width: 12em; min-width: 12em;
} }
}
}

View file

@ -22,14 +22,13 @@
--chromeHeight1: 70px; --chromeHeight1: 70px;
.app-content-container { .app-content-container {
width:100%; width: 100%;
height:100%; height: 100%;
#app-content { #app-content {
width:100%; width: 100%;
height:100%; height: 100%;
.fs-search { .fs-search {
.search-input--icon { .search-input--icon {
width: 4em; width: 4em;
background-size: 40%; background-size: 40%;
@ -38,7 +37,7 @@
input { input {
padding-left: 2em; padding-left: 2em;
font-size: 2em; font-size: 2em;
border-radius: var(--mediaItemRadius) border-radius: var(--mediaItemRadius);
} }
} }
} }
@ -56,42 +55,41 @@
z-index: 9999; z-index: 9999;
.top-nav-group { .top-nav-group {
background : #1e1e1e99; background: #1e1e1e99;
border : 1px solid lighten(@baseColor, 8); border: 1px solid lighten(@baseColor, 8);
border-radius: 12px; border-radius: 12px;
display : flex; display: flex;
height : 55px; height: 55px;
width: 90%; width: 90%;
backdrop-filter: var(--glassFilter); backdrop-filter: var(--glassFilter);
.app-sidebar-item { .app-sidebar-item {
background-color: #1e1e1e00; background-color: #1e1e1e00;
border-radius : 10px !important; border-radius: 10px !important;
border : 0px; border: 0px;
min-width : 120px; min-width: 120px;
padding : 6px; padding: 6px;
justify-content : center; justify-content: center;
align-items : center; align-items: center;
margin : 0px; margin: 0px;
height : 100%; height: 100%;
position : relative; position: relative;
font-size: 1.1em; font-size: 1.1em;
font-weight: 500; font-weight: 500;
&:before { &:before {
--dist : 1px; --dist: 1px;
content : ''; content: "";
position : absolute; position: absolute;
top : var(--dist); top: var(--dist);
left : var(--dist); left: var(--dist);
right : var(--dist); right: var(--dist);
bottom : var(--dist); bottom: var(--dist);
background-color: #fff; background-color: #fff;
opacity : 0; opacity: 0;
border-radius : 10px; border-radius: 10px;
transform : scale(0.5); transform: scale(0.5);
transition : transform 0.2s ease-in-out, opacity 0.2s ease-in-out; transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
} }
&:after { &:after {
@ -103,8 +101,8 @@
&:before { &:before {
transition: transform 0.1s ease-in-out, opacity 0.1s ease-in-out; transition: transform 0.1s ease-in-out, opacity 0.1s ease-in-out;
opacity : .1; opacity: 0.1;
transform : scale(1); transform: scale(1);
} }
} }
@ -112,15 +110,15 @@
background-color: transparent; background-color: transparent;
&:before { &:before {
opacity : .2; opacity: 0.2;
transform: scale(1); transform: scale(1);
} }
} }
&.md-btn-primary { &.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); background-color: lighten(@baseColor, @colorMixRate * 5);
z-index : 1; z-index: 1;
} }
} }
} }
@ -164,7 +162,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100% width: 100%;
} }
.volume-button--small { .volume-button--small {
@ -178,7 +176,7 @@
width: 30px; width: 30px;
border: 0px; border: 0px;
box-shadow: unset; box-shadow: unset;
opacity: 0.70; opacity: 0.7;
background-image: url("./assets/feather/volume-2.svg"); background-image: url("./assets/feather/volume-2.svg");
} }
@ -190,7 +188,7 @@
background-image: url("./assets/feather/volume.svg"); background-image: url("./assets/feather/volume.svg");
} }
input[type=range] { input[type="range"] {
-webkit-appearance: none; -webkit-appearance: none;
height: 4px; height: 4px;
background: rgba(255, 255, 255, 0.4); background: rgba(255, 255, 255, 0.4);
@ -228,7 +226,6 @@
} }
} }
.background { .background {
position: absolute; position: absolute;
background-size: cover; background-size: cover;
@ -247,13 +244,10 @@
.bg-artwork-container .bg-artwork { .bg-artwork-container .bg-artwork {
filter: brightness(85%) saturate(95%) blur(180px) contrast(0.9) opacity(0.9); filter: brightness(85%) saturate(95%) blur(180px) contrast(0.9) opacity(0.9);
} }
} }
} }
.lyrics-col { .lyrics-col {
height: 62vh; height: 62vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -278,11 +272,9 @@
.lyric-line { .lyric-line {
font-size: 35px; font-size: 35px;
} }
} }
.queue-col { .queue-col {
width: 60vh; width: 60vh;
height: 62vh; height: 62vh;
@ -361,7 +353,8 @@
} }
.app-playback-controls { .app-playback-controls {
.song-artist, .song-name { .song-artist,
.song-name {
font-weight: 600; font-weight: 600;
text-align: center; text-align: center;
font-size: 0.9em; font-size: 0.9em;
@ -414,8 +407,6 @@
width: 100%; width: 100%;
text-align: center; text-align: center;
} }
} }
.app-playback-controls .song-progress { .app-playback-controls .song-progress {
@ -436,7 +427,7 @@
} }
&:hover { &:hover {
> input[type=range] { > input[type="range"] {
&::-webkit-slider-thumb { &::-webkit-slider-thumb {
opacity: 1; opacity: 1;
transform: scale(1); transform: scale(1);
@ -445,7 +436,7 @@
} }
} }
input[type=range] { input[type="range"] {
appearance: none; appearance: none;
width: 100%; width: 100%;
height: 4px; height: 4px;
@ -462,7 +453,7 @@
border-radius: 100%; border-radius: 100%;
background: var(--songProgressColor); background: var(--songProgressColor);
cursor: default; 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 { &::-moz-range-thumb {
@ -482,7 +473,6 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
} }
.cd-mediaitem-square { .cd-mediaitem-square {
@ -556,18 +546,18 @@
.playlist-page .playlist-display { .playlist-page .playlist-display {
width: 100%; width: 100%;
max-width: 500px; max-width: 500px;
flex:1; flex: 1;
text-align: center; text-align: center;
.playlistInfo { .playlistInfo {
>.row { > .row {
justify-content: center; justify-content: center;
} }
} }
.playlist-controls { .playlist-controls {
div { div {
width:100%; width: 100%;
} }
} }
} }

View file

@ -156,8 +156,7 @@
} }
.close-btn { .close-btn {
.menu-panel.menu-header-text.close-btn .menu-panel.menu-header-text.close-btn;
} }
} }
} }
@ -180,7 +179,7 @@
} }
.close-btn { .close-btn {
.menu-panel.menu-header-text.close-btn .menu-panel.menu-header-text.close-btn;
} }
} }
@ -294,7 +293,6 @@
overflow: hidden; overflow: hidden;
font-size: 13px; font-size: 13px;
.menu-option { .menu-option {
text-align: left; text-align: left;
display: flex; display: flex;
@ -323,7 +321,7 @@
opacity: 0; opacity: 0;
transform: scale(0.98); transform: scale(0.98);
z-index: -1; z-index: -1;
transition: transform .25s ease-out, opacity .25s ease-out; transition: transform 0.25s ease-out, opacity 0.25s ease-out;
} }
&:hover { &:hover {
@ -336,7 +334,7 @@
&:active { &:active {
&::before { &::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; opacity: 1;
transform: scale(0.98); transform: scale(0.98);
background: var(--selected-click); background: var(--selected-click);
@ -372,7 +370,7 @@
} }
&:hover { &:hover {
background-color: rgb(196, 43, 28) background-color: rgb(196, 43, 28);
} }
} }
} }
@ -430,7 +428,6 @@
} }
} }
.moreinfo-modal { .moreinfo-modal {
.modal-window { .modal-window {
height: 70%; height: 70%;
@ -496,7 +493,8 @@
font-weight: 600; font-weight: 600;
} }
.song-artist, .song-album { .song-artist,
.song-album {
opacity: 0.75; opacity: 0.75;
cursor: pointer; cursor: pointer;
@ -521,4 +519,4 @@
&.svg-md { &.svg-md {
--size: 1.2em; --size: 1.2em;
} }
} }

View file

@ -1,59 +1,59 @@
// Linux // Linux
body[platform="linux"] { body[platform="linux"] {
#window-controls-container { #window-controls-container {
//display: none; //display: none;
} }
.window-controls { .window-controls {
justify-content: flex-end; justify-content: flex-end;
align-items : center; align-items: center;
padding-right : 6px; padding-right: 6px;
>div { > div {
--iconSize: 16px; --iconSize: 16px;
&.close, &.close,
&.minmax, &.minmax,
&.minimize, &.minimize,
&.minmax.restore { &.minmax.restore {
background-image: unset!important; background-image: unset !important;
position : relative; position: relative;
display : grid; display: grid;
align-content : center; align-content: center;
text-align : center; text-align: center;
height : 36px!important; height: 36px !important;
width : 36px!important; width: 36px !important;
border-radius : 50px; border-radius: 50px;
transition: background-color .1s ease-in-out; transition: background-color 0.1s ease-in-out;
&:hover { &:hover {
background: rgb(200 200 200 / 10%)!important; 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 : "";
}
} }
}
&.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: "";
}
} }
} }
}

View file

@ -1,79 +1,79 @@
body[platform="darwin"] { body[platform="darwin"] {
html { html {
background: transparent !important; background: transparent !important;
}
&.notransparency::before {
display: none;
}
#app {
&.simplebg {
background: transparent;
} }
&.notransparency::before { &::before {
display: none; display: none;
} }
#app { .app-chrome {
&.simplebg { background-color: var(--macOSChromeColor);
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-main { &.twopanel {
background-color: transparent; --chromeHeight1: 55px;
--chromeHeight: calc(var(--chromeHeight1) + var(--chromeHeight2));
.app-navigation { .app-chrome .app-chrome-item.search {
background: transparent; margin-right: 12px;
} }
#app-content { .app-chrome .app-mainmenu {
background-color: #1e1e1e6b; width: 46px;
} }
.app-chrome.chrome-bottom {
background-color: var(--macOSChromeColor);
}
} }
.settings-window.maxed { &[window-state="normal"] {
.tabs>.col-auto { &::after {
transition: padding-top .3s linear; position: fixed;
padding-top: var(--chromeHeight1); 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 { #app-content {
margin-top: 18px; background-color: #1e1e1e6b;
left: 70px;
} }
} }
.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;
}
}

View file

@ -1,7 +1,4 @@
#app.macosemu { #app.macosemu {
.app-chrome .app-chrome-item > .window-controls-macos { .app-chrome .app-chrome-item > .window-controls-macos {
@controlSize: 12px; @controlSize: 12px;
display: flex; display: flex;
@ -42,8 +39,8 @@
} }
} }
} }
.usermenu-body{ .usermenu-body {
left: calc(100vw - 260px); left: calc(100vw - 260px);
position: relative; position: relative;
} }
} }

View file

@ -65,7 +65,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100% width: 100%;
} }
.volume-button--small { .volume-button--small {
@ -79,7 +79,7 @@
width: 30px; width: 30px;
border: 0px; border: 0px;
box-shadow: unset; box-shadow: unset;
opacity: 0.70; opacity: 0.7;
background-image: url("./assets/feather/volume-2.svg"); background-image: url("./assets/feather/volume-2.svg");
} }
@ -91,7 +91,7 @@
background-image: url("./assets/feather/volume.svg"); background-image: url("./assets/feather/volume.svg");
} }
input[type=range] { input[type="range"] {
-webkit-appearance: none; -webkit-appearance: none;
height: 4px; height: 4px;
background: rgba(255, 255, 255, 0.4); background: rgba(255, 255, 255, 0.4);
@ -129,7 +129,6 @@
} }
} }
.background { .background {
position: absolute; position: absolute;
background-size: cover; background-size: cover;
@ -157,9 +156,7 @@
} }
} }
.lyrics-col { .lyrics-col {
height: 62vh; height: 62vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -184,11 +181,9 @@
.lyric-line { .lyric-line {
font-size: 35px; font-size: 35px;
} }
} }
.queue-col { .queue-col {
width: 60vh; width: 60vh;
height: 50vh; height: 50vh;
@ -281,11 +276,11 @@
} }
} }
.app-playback-controls { .app-playback-controls {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
.song-artist, .song-name { .song-artist,
.song-name {
font-weight: 600; font-weight: 600;
text-align: center; text-align: center;
font-size: 0.9em; font-size: 0.9em;
@ -338,8 +333,6 @@
width: 100%; width: 100%;
text-align: center; text-align: center;
} }
} }
.app-playback-controls .song-progress { .app-playback-controls .song-progress {
@ -360,7 +353,7 @@
} }
&:hover { &:hover {
> input[type=range] { > input[type="range"] {
&::-webkit-slider-thumb { &::-webkit-slider-thumb {
opacity: 1; opacity: 1;
transform: scale(1); transform: scale(1);
@ -369,7 +362,7 @@
} }
} }
input[type=range] { input[type="range"] {
appearance: none; appearance: none;
width: 100%; width: 100%;
height: 4px; height: 4px;
@ -386,7 +379,7 @@
border-radius: 100%; border-radius: 100%;
background: var(--songProgressColor); background: var(--songProgressColor);
cursor: default; 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 { &::-moz-range-thumb {
@ -405,6 +398,5 @@
width: 100%; width: 100%;
justify-content: center; justify-content: center;
} }
} }
} }

View file

@ -1,370 +1,370 @@
@-webkit-keyframes notyf-fadeinup { @-webkit-keyframes notyf-fadeinup {
0% { 0% {
opacity: 0; opacity: 0;
transform: translateY(25%) transform: translateY(25%);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0) transform: translateY(0);
} }
} }
@keyframes notyf-fadeinup { @keyframes notyf-fadeinup {
0% { 0% {
opacity: 0; opacity: 0;
transform: translateY(25%) transform: translateY(25%);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0) transform: translateY(0);
} }
} }
@-webkit-keyframes notyf-fadeinleft { @-webkit-keyframes notyf-fadeinleft {
0% { 0% {
opacity: 0; opacity: 0;
transform: translateX(25%) transform: translateX(25%);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateX(0) transform: translateX(0);
} }
} }
@keyframes notyf-fadeinleft { @keyframes notyf-fadeinleft {
0% { 0% {
opacity: 0; opacity: 0;
transform: translateX(25%) transform: translateX(25%);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateX(0) transform: translateX(0);
} }
} }
@-webkit-keyframes notyf-fadeoutright { @-webkit-keyframes notyf-fadeoutright {
0% { 0% {
opacity: 1; opacity: 1;
transform: translateX(0) transform: translateX(0);
} }
to { to {
opacity: 0; opacity: 0;
transform: translateX(25%) transform: translateX(25%);
} }
} }
@keyframes notyf-fadeoutright { @keyframes notyf-fadeoutright {
0% { 0% {
opacity: 1; opacity: 1;
transform: translateX(0) transform: translateX(0);
} }
to { to {
opacity: 0; opacity: 0;
transform: translateX(25%) transform: translateX(25%);
} }
} }
@-webkit-keyframes notyf-fadeoutdown { @-webkit-keyframes notyf-fadeoutdown {
0% { 0% {
opacity: 1; opacity: 1;
transform: translateY(0) transform: translateY(0);
} }
to { to {
opacity: 0; opacity: 0;
transform: translateY(25%) transform: translateY(25%);
} }
} }
@keyframes notyf-fadeoutdown { @keyframes notyf-fadeoutdown {
0% { 0% {
opacity: 1; opacity: 1;
transform: translateY(0) transform: translateY(0);
} }
to { to {
opacity: 0; opacity: 0;
transform: translateY(25%) transform: translateY(25%);
} }
} }
@-webkit-keyframes ripple { @-webkit-keyframes ripple {
0% { 0% {
transform: scale(0) translateY(-45%) translateX(13%) transform: scale(0) translateY(-45%) translateX(13%);
} }
to { to {
transform: scale(1) translateY(-45%) translateX(13%) transform: scale(1) translateY(-45%) translateX(13%);
} }
} }
@keyframes ripple { @keyframes ripple {
0% { 0% {
transform: scale(0) translateY(-45%) translateX(13%) transform: scale(0) translateY(-45%) translateX(13%);
} }
to { to {
transform: scale(1) translateY(-45%) translateX(13%) transform: scale(1) translateY(-45%) translateX(13%);
} }
} }
.notyf { .notyf {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
height: 100%; height: 100%;
width: 100%; width: 100%;
color: #fff; color: #fff;
z-index: 9999; z-index: 9999;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-end; align-items: flex-end;
justify-content: flex-end; justify-content: flex-end;
pointer-events: none; pointer-events: none;
box-sizing: border-box; box-sizing: border-box;
padding: 20px padding: 20px;
} }
.notyf__icon--error, .notyf__icon--error,
.notyf__icon--success { .notyf__icon--success {
height: 21px; height: 21px;
width: 21px; width: 21px;
background: #fff; background: #fff;
border-radius: 50%; border-radius: 50%;
display: block; display: block;
margin: 0 auto; margin: 0 auto;
position: relative position: relative;
} }
.notyf__icon--error:after, .notyf__icon--error:after,
.notyf__icon--error:before { .notyf__icon--error:before {
content: ""; content: "";
background: currentColor; background: currentColor;
display: block; display: block;
position: absolute; position: absolute;
width: 3px; width: 3px;
border-radius: 3px; border-radius: 3px;
left: 9px; left: 9px;
height: 12px; height: 12px;
top: 5px top: 5px;
} }
.notyf__icon--error:after { .notyf__icon--error:after {
transform: rotate(-45deg) transform: rotate(-45deg);
} }
.notyf__icon--error:before { .notyf__icon--error:before {
transform: rotate(45deg) transform: rotate(45deg);
} }
.notyf__icon--success:after, .notyf__icon--success:after,
.notyf__icon--success:before { .notyf__icon--success:before {
content: ""; content: "";
background: currentColor; background: currentColor;
display: block; display: block;
position: absolute; position: absolute;
width: 3px; width: 3px;
border-radius: 3px border-radius: 3px;
} }
.notyf__icon--success:after { .notyf__icon--success:after {
height: 6px; height: 6px;
transform: rotate(-45deg); transform: rotate(-45deg);
top: 9px; top: 9px;
left: 6px left: 6px;
} }
.notyf__icon--success:before { .notyf__icon--success:before {
height: 11px; height: 11px;
transform: rotate(45deg); transform: rotate(45deg);
top: 5px; top: 5px;
left: 10px left: 10px;
} }
.notyf__toast { .notyf__toast {
display: block; display: block;
overflow: hidden; overflow: hidden;
pointer-events: auto; pointer-events: auto;
-webkit-animation: notyf-fadeinup .3s ease-in forwards; -webkit-animation: notyf-fadeinup 0.3s ease-in forwards;
animation: notyf-fadeinup .3s ease-in forwards; animation: notyf-fadeinup 0.3s ease-in forwards;
box-shadow: 0 3px 7px 0 rgba(0, 0, 0, .25); box-shadow: 0 3px 7px 0 rgba(0, 0, 0, 0.25);
position: relative; position: relative;
padding: 0 15px; padding: 0 15px;
border-radius: 2px; border-radius: 2px;
max-width: 300px; max-width: 300px;
transform: translateY(25%); transform: translateY(25%);
box-sizing: border-box; box-sizing: border-box;
flex-shrink: 0 flex-shrink: 0;
} }
.notyf__toast--disappear { .notyf__toast--disappear {
transform: translateY(0); transform: translateY(0);
-webkit-animation: notyf-fadeoutdown .3s forwards; -webkit-animation: notyf-fadeoutdown 0.3s forwards;
animation: notyf-fadeoutdown .3s forwards; animation: notyf-fadeoutdown 0.3s forwards;
-webkit-animation-delay: .25s; -webkit-animation-delay: 0.25s;
animation-delay: .25s animation-delay: 0.25s;
} }
.notyf__toast--disappear .notyf__icon, .notyf__toast--disappear .notyf__icon,
.notyf__toast--disappear .notyf__message { .notyf__toast--disappear .notyf__message {
-webkit-animation: notyf-fadeoutdown .3s forwards; -webkit-animation: notyf-fadeoutdown 0.3s forwards;
animation: notyf-fadeoutdown .3s forwards; animation: notyf-fadeoutdown 0.3s forwards;
opacity: 1; opacity: 1;
transform: translateY(0) transform: translateY(0);
} }
.notyf__toast--disappear .notyf__dismiss { .notyf__toast--disappear .notyf__dismiss {
-webkit-animation: notyf-fadeoutright .3s forwards; -webkit-animation: notyf-fadeoutright 0.3s forwards;
animation: notyf-fadeoutright .3s forwards; animation: notyf-fadeoutright 0.3s forwards;
opacity: 1; opacity: 1;
transform: translateX(0) transform: translateX(0);
} }
.notyf__toast--disappear .notyf__message { .notyf__toast--disappear .notyf__message {
-webkit-animation-delay: .05s; -webkit-animation-delay: 0.05s;
animation-delay: .05s animation-delay: 0.05s;
} }
.notyf__toast--upper { .notyf__toast--upper {
margin-bottom: 20px margin-bottom: 20px;
} }
.notyf__toast--lower { .notyf__toast--lower {
margin-top: 20px margin-top: 20px;
} }
.notyf__toast--dismissible .notyf__wrapper { .notyf__toast--dismissible .notyf__wrapper {
padding-right: 30px padding-right: 30px;
} }
.notyf__ripple { .notyf__ripple {
height: 400px; height: 400px;
width: 400px; width: 400px;
position: absolute; position: absolute;
transform-origin: bottom right; transform-origin: bottom right;
right: 0; right: 0;
top: 0; top: 0;
border-radius: 50%; border-radius: 50%;
transform: scale(0) translateY(-51%) translateX(13%); transform: scale(0) translateY(-51%) translateX(13%);
z-index: 5; z-index: 5;
-webkit-animation: ripple .4s ease-out forwards; -webkit-animation: ripple 0.4s ease-out forwards;
animation: ripple .4s ease-out forwards animation: ripple 0.4s ease-out forwards;
} }
.notyf__wrapper { .notyf__wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
padding-top: 17px; padding-top: 17px;
padding-bottom: 17px; padding-bottom: 17px;
padding-right: 15px; padding-right: 15px;
border-radius: 3px; border-radius: 3px;
position: relative; position: relative;
z-index: 10 z-index: 10;
} }
.notyf__icon { .notyf__icon {
width: 22px; width: 22px;
text-align: center; text-align: center;
font-size: 1.3em; font-size: 1.3em;
opacity: 0; opacity: 0;
-webkit-animation: notyf-fadeinup .3s forwards; -webkit-animation: notyf-fadeinup 0.3s forwards;
animation: notyf-fadeinup .3s forwards; animation: notyf-fadeinup 0.3s forwards;
-webkit-animation-delay: .3s; -webkit-animation-delay: 0.3s;
animation-delay: .3s; animation-delay: 0.3s;
margin-right: 13px margin-right: 13px;
} }
.notyf__dismiss { .notyf__dismiss {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
height: 100%; height: 100%;
width: 26px; width: 26px;
margin-right: -15px; margin-right: -15px;
-webkit-animation: notyf-fadeinleft .3s forwards; -webkit-animation: notyf-fadeinleft 0.3s forwards;
animation: notyf-fadeinleft .3s forwards; animation: notyf-fadeinleft 0.3s forwards;
-webkit-animation-delay: .35s; -webkit-animation-delay: 0.35s;
animation-delay: .35s; animation-delay: 0.35s;
opacity: 0 opacity: 0;
} }
.notyf__dismiss-btn { .notyf__dismiss-btn {
background-color: rgba(0, 0, 0, .25); background-color: rgba(0, 0, 0, 0.25);
border: none; border: none;
cursor: pointer; cursor: pointer;
transition: opacity .2s ease, background-color .2s ease; transition: opacity 0.2s ease, background-color 0.2s ease;
outline: none; outline: none;
opacity: .35; opacity: 0.35;
height: 100%; height: 100%;
width: 100% width: 100%;
} }
.notyf__dismiss-btn:after, .notyf__dismiss-btn:after,
.notyf__dismiss-btn:before { .notyf__dismiss-btn:before {
content: ""; content: "";
background: #fff; background: #fff;
height: 12px; height: 12px;
width: 2px; width: 2px;
border-radius: 3px; border-radius: 3px;
position: absolute; position: absolute;
left: calc(50% - 1px); left: calc(50% - 1px);
top: calc(50% - 5px) top: calc(50% - 5px);
} }
.notyf__dismiss-btn:after { .notyf__dismiss-btn:after {
transform: rotate(-45deg) transform: rotate(-45deg);
} }
.notyf__dismiss-btn:before { .notyf__dismiss-btn:before {
transform: rotate(45deg) transform: rotate(45deg);
} }
.notyf__dismiss-btn:hover { .notyf__dismiss-btn:hover {
opacity: .7; opacity: 0.7;
background-color: rgba(0, 0, 0, .15) background-color: rgba(0, 0, 0, 0.15);
} }
.notyf__dismiss-btn:active { .notyf__dismiss-btn:active {
opacity: .8 opacity: 0.8;
} }
.notyf__message { .notyf__message {
vertical-align: middle; vertical-align: middle;
position: relative; position: relative;
opacity: 0; opacity: 0;
-webkit-animation: notyf-fadeinup .3s forwards; -webkit-animation: notyf-fadeinup 0.3s forwards;
animation: notyf-fadeinup .3s forwards; animation: notyf-fadeinup 0.3s forwards;
-webkit-animation-delay: .25s; -webkit-animation-delay: 0.25s;
animation-delay: .25s; animation-delay: 0.25s;
line-height: 1.5em line-height: 1.5em;
} }
@media only screen and (max-width:480px) { @media only screen and (max-width: 480px) {
.notyf { .notyf {
padding: 0 padding: 0;
} }
.notyf__ripple { .notyf__ripple {
height: 600px; height: 600px;
width: 600px; width: 600px;
-webkit-animation-duration: .5s; -webkit-animation-duration: 0.5s;
animation-duration: .5s animation-duration: 0.5s;
} }
.notyf__toast { .notyf__toast {
max-width: none; max-width: none;
border-radius: 0; border-radius: 0;
box-shadow: 0 -2px 7px 0 rgba(0, 0, 0, .13); box-shadow: 0 -2px 7px 0 rgba(0, 0, 0, 0.13);
width: 100% width: 100%;
} }
.notyf__dismiss { .notyf__dismiss {
width: 56px width: 56px;
} }
} }

View file

@ -1277,7 +1277,7 @@
} }
.audiolabs-page .spprofile-line .spprofile-viewport .spprev:before, .audiolabs-page .spprofile-line .spprofile-viewport .spprev:before,
.audiolabs-page .spprofile-line .spprofile-viewport .nextprev:before { .audiolabs-page .spprofile-line .spprofile-viewport .nextprev:before {
content: ''; content: "";
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -1309,7 +1309,7 @@
background: black; background: black;
} }
.audiolabs-page .spprofile-line .spprofile-viewport .spslide > img { .audiolabs-page .spprofile-line .spprofile-viewport .spslide > img {
WIDTH: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }

View file

@ -242,7 +242,6 @@
} }
.list-group-item { .list-group-item {
&:hover { &:hover {
cursor: grab; cursor: grab;
} }
@ -294,7 +293,6 @@
// Search Page // Search Page
&.search-page { &.search-page {
.searchToggle { .searchToggle {
float: right; float: right;
@ -302,7 +300,7 @@
min-width: 120px; min-width: 120px;
} }
} }
.categories{ .categories {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(200px, 1fr)); grid-template-columns: repeat(3, minmax(200px, 1fr));
gap: 1rem; gap: 1rem;
@ -316,7 +314,7 @@
width: 100% !important; width: 100% !important;
z-index: 1; z-index: 1;
} }
.info-rect{ .info-rect {
height: max-content; height: max-content;
} }
.title { .title {
@ -452,7 +450,7 @@
right: 0; right: 0;
&:hover { &:hover {
background-color: rgb(196, 43, 28) background-color: rgb(196, 43, 28);
} }
} }
@ -488,10 +486,7 @@
display: block; display: block;
line-break: anywhere; line-break: anywhere;
} }
} }
} }
// Podcast Page // Podcast Page
@ -622,7 +617,7 @@
right: 0; right: 0;
&:hover { &:hover {
background-color: rgb(196, 43, 28) background-color: rgb(196, 43, 28);
} }
} }
@ -657,10 +652,7 @@
display: block; display: block;
line-break: anywhere; line-break: anywhere;
} }
} }
} }
@media only screen and (max-width: 1230px) { @media only screen and (max-width: 1230px) {
@ -828,7 +820,7 @@
margin-bottom: -10px; margin-bottom: -10px;
padding: 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%); -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); animation: playlistArtworkFadeIn 1s var(--appleEase);
.artworkMaterial img { .artworkMaterial img {
@ -898,7 +890,7 @@
} }
.search-input::placeholder { .search-input::placeholder {
color: var(--heroplaceholdercolor) color: var(--heroplaceholdercolor);
} }
.nameEdit { .nameEdit {
@ -939,7 +931,7 @@
} }
.playlist-desc { .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; box-sizing: border-box;
font-size: 14px; font-size: 14px;
flex-shrink: unset; flex-shrink: unset;
@ -1043,8 +1035,6 @@
} }
} }
} }
} }
.friends-info { .friends-info {
@ -1061,7 +1051,7 @@
border-radius: 100%; border-radius: 100%;
overflow: hidden; overflow: hidden;
box-shadow: var(--mediaItemShadow-ShadowSubtle); box-shadow: var(--mediaItemShadow-ShadowSubtle);
transition: transform .2s var(--appleEase); transition: transform 0.2s var(--appleEase);
margin: 6px; margin: 6px;
&:hover { &:hover {
@ -1081,7 +1071,7 @@
font-size: 0.9em; font-size: 0.9em;
margin: 6px; margin: 6px;
opacity: 0.7; 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; height: 0.9em;
} }
@ -1151,13 +1141,13 @@
} }
.playlist-time { .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; height: 0px;
opacity: 0; opacity: 0;
} }
.playlist-desc { .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; height: 0px !important;
opacity: 0; opacity: 0;
} }
@ -1272,7 +1262,6 @@
} }
} }
.artworkContainer { .artworkContainer {
position: absolute; position: absolute;
top: 0; top: 0;
@ -1282,7 +1271,7 @@
margin: 0; margin: 0;
padding: 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%); -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); animation: playlistArtworkFadeIn 1s var(--appleEase);
.artworkMaterial img { .artworkMaterial img {
@ -1412,7 +1401,6 @@
} }
.artist-title { .artist-title {
.artist-play { .artist-play {
transform: translateY(3px); transform: translateY(3px);
margin: 14px; margin: 14px;
@ -1490,7 +1478,6 @@
width: 90%; width: 90%;
margin: 16px auto 0px; margin: 16px auto 0px;
} }
} }
// AudioLabs page // AudioLabs page
@ -1502,7 +1489,7 @@
border-bottom: unset; border-bottom: unset;
border-top: unset; border-top: unset;
font-weight: 600; font-weight: 600;
font-size: 1.0em; font-size: 1em;
background: rgb(255 255 255 / 3%); background: rgb(255 255 255 / 3%);
} }
@ -1548,7 +1535,7 @@
} }
&:before { &:before {
content: ''; content: "";
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -1568,7 +1555,6 @@
&:before { &:before {
-webkit-mask-image: url("./views/svg/chevron-left.svg"); -webkit-mask-image: url("./views/svg/chevron-left.svg");
} }
} }
.nextprev { .nextprev {
@ -1577,7 +1563,6 @@
&:before { &:before {
-webkit-mask-image: url("./views/svg/chevron-right.svg"); -webkit-mask-image: url("./views/svg/chevron-right.svg");
} }
} }
.spslide { .spslide {
@ -1588,7 +1573,7 @@
background: black; background: black;
> img { > img {
WIDTH: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
@ -1646,7 +1631,6 @@
//Home //Home
.home-page { .home-page {
.md-btn-replay { .md-btn-replay {
background-image: linear-gradient(-45deg, #2e2173, #925042); background-image: linear-gradient(-45deg, #2e2173, #925042);
animation: gradient-animation 5s ease-in-out infinite; animation: gradient-animation 5s ease-in-out infinite;
@ -1738,8 +1722,8 @@
border-radius: var(--mediaItemRadius); border-radius: var(--mediaItemRadius);
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
transition: transform .2s var(--appleEase); transition: transform 0.2s var(--appleEase);
transition-delay: .1s; transition-delay: 0.1s;
align-self: center; align-self: center;
&:hover { &:hover {
@ -1780,7 +1764,6 @@
} }
.top-genres-container { .top-genres-container {
.genre-name { .genre-name {
font-size: 0.9em; font-size: 0.9em;
margin: 6px 0px; margin: 6px 0px;
@ -1810,11 +1793,11 @@
.cd-mediaitem-square { .cd-mediaitem-square {
.mediaitem-artwork { .mediaitem-artwork {
animation: replayFadeIn .5s var(--appleEase); animation: replayFadeIn 0.5s var(--appleEase);
} }
transition: transform .2s var(--appleEase); transition: transform 0.2s var(--appleEase);
transition-delay: .1s; transition-delay: 0.1s;
&:hover { &:hover {
transform: scale(1.1); transform: scale(1.1);
@ -1883,7 +1866,6 @@
height: 100%; height: 100%;
width: 100%; width: 100%;
.oobe-header { .oobe-header {
font-size: 3em; font-size: 3em;
text-shadow: var(--replayTextShadow); text-shadow: var(--replayTextShadow);
@ -1927,7 +1909,7 @@
.visualPreview { .visualPreview {
pointer-events: none; pointer-events: none;
transition: .25s all; transition: 0.25s all;
width: 100%; width: 100%;
} }
@ -1954,9 +1936,8 @@
outline: 4px solid var(--keyColor); outline: 4px solid var(--keyColor);
} }
&:hover { &:hover {
transform: scale(1.10) translateZ(-1px) translateY(10px); transform: scale(1.1) translateZ(-1px) translateY(10px);
z-index: 1; z-index: 1;
box-shadow: 0px 12px 16px rgb(0 0 0 / 25%); box-shadow: 0px 12px 16px rgb(0 0 0 / 25%);
} }
@ -1981,10 +1962,8 @@
text-align: center; text-align: center;
} }
} }
} }
.oobe-titlebar { .oobe-titlebar {
position: absolute; position: absolute;
top: 0; top: 0;
@ -2125,7 +2104,6 @@
.nav-pills { .nav-pills {
gap: 6px; gap: 6px;
} }
.nav-pills .nav-link { .nav-pills .nav-link {
@ -2139,7 +2117,6 @@
} }
} }
.md-option-header { .md-option-header {
padding: 0px 26px; padding: 0px 26px;
border-bottom: unset; border-bottom: unset;
@ -2192,7 +2169,7 @@
} }
&:hover { &:hover {
background-color: rgb(196, 43, 28) background-color: rgb(196, 43, 28);
} }
&.back-btn { &.back-btn {
@ -2241,35 +2218,36 @@
overflow-y: overlay; overflow-y: overlay;
height: 100%; height: 100%;
background-color: var(--panelColor2); background-color: var(--panelColor2);
padding:0px; padding: 0px;
padding-top: 48px; padding-top: 48px;
border-left: 1px solid var(--borderColor); border-left: 1px solid var(--borderColor);
} }
.github-themes-page, .installed-themes-page { .github-themes-page,
.installed-themes-page {
.header-text { .header-text {
font-size: 1.25em; font-size: 1.25em;
} }
} }
.tab-pane { .tab-pane {
height:100%; height: 100%;
} }
.settings-tab-content { .settings-tab-content {
height:100%; height: 100%;
} }
&.no-sidebar { &.no-sidebar {
.gh-header { .gh-header {
>.row { > .row {
&:last-child { &:last-child {
padding-right: 90px; padding-right: 90px;
} }
} }
} }
.tab-content { .tab-content {
padding-top:0px; padding-top: 0px;
} }
.tabs { .tabs {
@ -2277,10 +2255,10 @@
width: 50px; width: 50px;
:nth-child(2) { :nth-child(2) {
// font-size: 0px; // font-size: 0px;
opacity:0; opacity: 0;
} }
} }
>.col-auto { > .col-auto {
width: 80px; width: 80px;
} }
} }
@ -2290,4 +2268,4 @@
#hid___BV_tab_button__ { #hid___BV_tab_button__ {
display: none; display: none;
} }

View file

@ -1,48 +1,46 @@
import { app } from "./vueapp.js" import { app } from "./vueapp.js";
import {CiderCache} from './cidercache.js' import { CiderCache } from "./cidercache.js";
import {CiderFrontAPI} from './ciderfrontapi.js' import { CiderFrontAPI } from "./ciderfrontapi.js";
import {simulateGamepad} from './gamepad.js' import { simulateGamepad } from "./gamepad.js";
import {CiderAudio} from '../audio/cideraudio.js' import { CiderAudio } from "../audio/cideraudio.js";
import {Events} from './events.js' import { Events } from "./events.js";
import { wsapi } from "./wsapi_interop.js" import { wsapi } from "./wsapi_interop.js";
import { MusicKitTools } from "./musickittools.js" import { MusicKitTools } from "./musickittools.js";
import { spawnMica } from "./mica.js" import { spawnMica } from "./mica.js";
import { svgIcon } from './components/svg-icon.js' import { svgIcon } from "./components/svg-icon.js";
import { sidebarLibraryItem } from './components/sidebar-library-item.js' import { sidebarLibraryItem } from "./components/sidebar-library-item.js";
// Define window objects // Define window objects
window.app = app window.app = app;
window.MusicKitTools = MusicKitTools window.MusicKitTools = MusicKitTools;
window.CiderAudio = CiderAudio window.CiderAudio = CiderAudio;
window.CiderCache = CiderCache window.CiderCache = CiderCache;
window.CiderFrontAPI = CiderFrontAPI window.CiderFrontAPI = CiderFrontAPI;
window.wsapi = wsapi window.wsapi = wsapi;
if (app.cfg.advanced.disableLogging === true) { if (app.cfg.advanced.disableLogging === true) {
window.console = { window.console = {
log: function() {}, log: function () {},
error: function() {}, error: function () {},
warn: function() {}, warn: function () {},
assert: function() {}, assert: function () {},
debug: function() {} debug: function () {},
} };
} }
// Mount Vue to #app // Mount Vue to #app
app.$mount("#app") app.$mount("#app");
// Init CiderAudio and force audiocontext // Init CiderAudio and force audiocontext
if (app.cfg.advanced.AudioContext != true) { if (app.cfg.advanced.AudioContext != true) {
app.cfg.advanced.AudioContext = true; app.cfg.advanced.AudioContext = true;
window.location.reload(); window.location.reload();
} }
CiderAudio.init() CiderAudio.init();
// Import gamepad support // Import gamepad support
app.simulateGamepad = simulateGamepad app.simulateGamepad = simulateGamepad;
app.spawnMica = spawnMica app.spawnMica = spawnMica;
Events.InitEvents() Events.InitEvents();

View file

@ -1,24 +1,24 @@
const CiderCache = { const CiderCache = {
async getCache(file) { async getCache(file) {
let cache = await ipcRenderer.sendSync("get-cache", file) let cache = await ipcRenderer.sendSync("get-cache", file);
if (isJson(cache)) { if (isJson(cache)) {
cache = JSON.parse(cache) cache = JSON.parse(cache);
if (Object.keys(cache).length === 0) { if (Object.keys(cache).length === 0) {
cache = false cache = false;
} }
} else { } else {
cache = false cache = false;
}
return cache
},
async putCache(file, data) {
console.log(`Caching ${file}`)
ipcRenderer.invoke("put-cache", {
file: file,
data: JSON.stringify(data)
})
return true
} }
} 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 };

View file

@ -1,32 +1,31 @@
const CiderFrontAPI = { const CiderFrontAPI = {
Objects: { Objects: {
MenuEntry: function () { MenuEntry: function () {
this.id = "" this.id = "";
this.name = "" this.name = "";
this.onClick = () => { this.onClick = () => {};
}
}
}, },
AddMenuEntry(entry) { },
app.pluginMenuEntries.push(entry) AddMenuEntry(entry) {
app.pluginInstalled = true 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 };

View file

@ -1,46 +1,45 @@
import {html} from "../html.js" import { html } from "../html.js";
export const sidebarLibraryItem = Vue.component("sidebar-library-item", { export const sidebarLibraryItem = Vue.component("sidebar-library-item", {
template: html` template: html`
<button class="app-sidebar-item" <button class="app-sidebar-item" :class="$root.getSidebarItemClass(page)" @click="$root.setWindowHash(page)">
:class="$root.getSidebarItemClass(page)" @click="$root.setWindowHash(page)"> <svg-icon :url="svgIconData" :name="'sidebar-' + svgIconName" v-if="svgIconData != ''" />
<svg-icon :url="svgIconData" :name="'sidebar-' + svgIconName" v-if="svgIconData != ''"/> <span class="sidebar-item-text">{{ name }}</span>
<span class="sidebar-item-text">{{ name }}</span> </button>
</button> `,
`, props: {
props: { name: {
name: { type: String,
type: String, required: true,
required: true,
},
page: {
type: String,
required: true,
},
svgIcon: {
type: String,
required: false,
default: "",
},
svgIconName: {
type: String,
required: false
},
cdClick: {
type: Function,
required: false,
},
}, },
data: function () { page: {
return { type: String,
app: app, required: true,
svgIconData: "",
};
}, },
async mounted() { svgIcon: {
if (this.svgIcon) { type: String,
this.svgIconData = this.svgIcon; 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: {},
});

View file

@ -1,22 +1,20 @@
import {html} from "../html.js" import { html } from "../html.js";
export const svgIcon = Vue.component("svg-icon", { export const svgIcon = Vue.component("svg-icon", {
template: html` template: html` <div class="_svg-icon" :class="classes" :svg-name="name" :style="{'--icon': 'url(' + url + ')'}"></div> `,
<div class="_svg-icon" :class="classes" :svg-name="name" :style="{'--icon': 'url(' + url + ')'}"></div> props: {
`, name: {
props: { type: String,
name: { required: false,
type: String, },
required: false classes: {
}, type: String,
classes: { required: false,
type: String, },
required: false url: {
}, type: String,
url: { required: true,
type: String, default: "./assets/repeat.svg",
required: true, },
default: "./assets/repeat.svg" },
} });
}
})

View file

@ -1,98 +1,94 @@
const Events = { const Events = {
InitEvents() { InitEvents() {
const app = window.app const app = window.app;
// add event listener for when window.location.hash changes // add event listener for when window.location.hash changes
window.addEventListener("hashchange", function () { window.addEventListener("hashchange", function () {
app.page = "blank" app.page = "blank";
setTimeout(()=>{ setTimeout(() => {
app.appRoute(window.location.hash) app.appRoute(window.location.hash);
}, 100) }, 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) => { // CTRL + SHIFT + R
if (e.button === 3) { if (event.keyCode === 82 && event.ctrlKey && event.shiftKey) {
e.preventDefault() event.preventDefault();
app.navigateBack() window.location.reload();
} else if (e.button === 4) { }
e.preventDefault() // CTRL + E
app.navigateForward() 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) { // Hang Timer
// CTRL + R app.hangtimer = setTimeout(() => {
if (event.keyCode === 82 && event.ctrlKey) { if (confirm("Cider is not responding. Reload the app?")) {
event.preventDefault() window.location.reload();
app.confirm(app.getLz('term.reload'), (res)=>{ }
if (res) { }, 10000);
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()
} // Refresh Focus
}); function refreshFocus() {
if (document.hasFocus() == false) {
// Hang Timer app.windowFocus(false);
app.hangtimer = setTimeout(() => { } else {
if (confirm("Cider is not responding. Reload the app?")) { app.windowFocus(true);
window.location.reload() }
} setTimeout(refreshFocus, 200);
}, 10000)
// Refresh Focus
function refreshFocus() {
if (document.hasFocus() == false) {
app.windowFocus(false)
} else {
app.windowFocus(true)
}
setTimeout(refreshFocus, 200);
}
refreshFocus();
} }
}
export {Events} refreshFocus();
},
};
export { Events };

View file

@ -1,327 +1,313 @@
function simulateGamepad () { function simulateGamepad() {
const app = window.app const app = window.app;
app.chrome.showCursor = true app.chrome.showCursor = true;
let cursorPos = [0, 0]; let cursorPos = [0, 0];
let intTabIndex = 0 let intTabIndex = 0;
const cursorSpeedPvt = 8 const cursorSpeedPvt = 8;
const cursorSize = 16 const cursorSize = 16;
let scrollSpeed = 8 let scrollSpeed = 8;
let buttonPressDelay = 500 let buttonPressDelay = 500;
let stickDeadZone = 0.2 let stickDeadZone = 0.2;
let scrollGroup = null let scrollGroup = null;
let scrollGroupY = null let scrollGroupY = null;
let elementFocusEnabled = true let elementFocusEnabled = true;
let start; 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 = { var gp = gamepads[0];
Confirm: new Audio("./sounds/confirm.ogg"),
Menu: new Audio("./sounds/btn1.ogg"), // LEFT STICK
Hover: new Audio("./sounds/hover.ogg") 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) if (gp.axes[1] > stickDeadZone) {
let elementType = 0 cursorPos[1] += gp.axes[1] * cursorSpeed;
} else if (gp.axes[1] < -stickDeadZone) {
function appLoop() { cursorPos[1] += gp.axes[1] * cursorSpeed;
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);
} }
// controller pairing if (cursorPos[0] < cursorSize) {
notyf.error("Press the button on your controller to pair it to Cider.") cursorPos[0] = cursorSize;
window.addEventListener("gamepadconnected", function (e) { }
console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", if (cursorPos[1] < cursorSize) {
e.gamepad.index, e.gamepad.id, cursorPos[1] = cursorSize;
e.gamepad.buttons.length, e.gamepad.axes.length); }
notyf.success("Pairing successful!") if (cursorPos[0] > window.innerWidth - cursorSize) {
appLoop() cursorPos[0] = window.innerWidth - cursorSize;
}, { once: true }); }
if (cursorPos[1] > window.innerHeight - cursorSize) {
cursorPos[1] = window.innerHeight - cursorSize;
}
document.addEventListener("keydown", (e) => { // RIGHT STICK.
sounds.Confirm.currentTime = 0 if (scrollGroupY) {
sounds.Menu.currentTime = 0 if (gp.axes[3] > stickDeadZone) {
sounds.Hover.currentTime = 0 $(scrollGroupY).scrollTop($(scrollGroupY).scrollTop() + gp.axes[3] * scrollSpeed);
let tabbable = $("[tabindex]") elementFocusEnabled = false;
console.log(e.key) } else if (gp.axes[3] < -stickDeadZone) {
switch (e.key) { $(scrollGroupY).scrollTop($(scrollGroupY).scrollTop() + gp.axes[3] * scrollSpeed);
default: elementFocusEnabled = false;
break; } else {
case "ArrowLeft": elementFocusEnabled = true;
e.preventDefault() }
}
cursorPos[0] -= cursorSpeed if (scrollGroup) {
break; if (gp.axes[2] > stickDeadZone) {
case "ArrowRight": $(scrollGroup).scrollLeft($(scrollGroup).scrollLeft() + gp.axes[2] * scrollSpeed);
e.preventDefault() elementFocusEnabled = false;
} else if (gp.axes[2] < -stickDeadZone) {
$(scrollGroup).scrollLeft($(scrollGroup).scrollLeft() + gp.axes[2] * scrollSpeed);
elementFocusEnabled = false;
} else {
elementFocusEnabled = true;
}
}
cursorPos[0] += cursorSpeed $(".cursor").css({
break; top: cursorPos[1] + "px",
case "ArrowUp": left: cursorPos[0] + "px",
e.preventDefault() display: "block",
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)
}); });
// 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 };

View file

@ -1,3 +1,3 @@
export function html (str) { export function html(str) {
return str[0] return str[0];
} }

View file

@ -31,9 +31,9 @@ async function spawnMica() {
} }
if (micaCache.path == imgSrc.path) { if (micaCache.path == imgSrc.path) {
imgSrc = micaCache; imgSrc = micaCache;
}else{ } else {
imgSrc = await ipcRenderer.sendSync("get-wallpaper", { imgSrc = await ipcRenderer.sendSync("get-wallpaper", {
blurAmount: 256 blurAmount: 256,
}); });
CiderCache.putCache("mica-cache", imgSrc); CiderCache.putCache("mica-cache", imgSrc);
} }
@ -51,10 +51,7 @@ async function spawnMica() {
cb(); cb();
} }
// window size change // window size change
if ( if (lastScreenWidth !== window.innerWidth || lastScreenHeight !== window.innerHeight) {
lastScreenWidth !== window.innerWidth ||
lastScreenHeight !== window.innerHeight
) {
lastScreenWidth = window.innerWidth; lastScreenWidth = window.innerWidth;
lastScreenHeight = window.innerHeight; lastScreenHeight = window.innerHeight;
cb(); cb();

View file

@ -1,56 +1,44 @@
const MusicKitTools = { const MusicKitTools = {
async v3Backend({ async v3Backend({ route = "", getBody = {}, options = {} }) {
route = "", getBody = {}, options = {} return await await ipcRenderer.invoke("mkv3", {
}) { token: MusicKit.getInstance().developerToken,
return await (await ipcRenderer.invoke("mkv3", { route: route,
token: MusicKit.getInstance().developerToken, mediaToken: MusicKit.getInstance().musicUserToken,
route: route, GETBody: getBody,
mediaToken: MusicKit.getInstance().musicUserToken, });
GETBody: getBody },
})) async v3Continuous({ href, options = {}, reqOptions = {}, onProgress = () => {}, onError = () => {}, onSuccess = () => {} } = {}) {
}, let returnData = [];
async v3Continuous({
href,
options = {},
reqOptions = {},
onProgress = () => {
},
onError = () => {
},
onSuccess = () => {
}
} = {}) {
let returnData = []
async function sendReq(href, options) { async function sendReq(href, options) {
const response = await app.mk.api.v3.music(href, options).catch(error => onError) const response = await app.mk.api.v3.music(href, options).catch((error) => onError);
returnData = returnData.concat(response.data.data) returnData = returnData.concat(response.data.data);
if (response.data.next) { if (response.data.next) {
onProgress({ onProgress({
response: response, response: response,
total: returnData.length 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
}); });
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

View file

@ -1,38 +1,38 @@
const store = new Vuex.Store({ const store = new Vuex.Store({
state: { state: {
windowRelativeScale: 1, windowRelativeScale: 1,
library: { library: {
// songs: ipcRenderer.sendSync("get-library-songs"), // songs: ipcRenderer.sendSync("get-library-songs"),
// albums: ipcRenderer.sendSync("get-library-albums"), // albums: ipcRenderer.sendSync("get-library-albums"),
// recentlyAdded: ipcRenderer.sendSync("get-library-recentlyAdded"), // recentlyAdded: ipcRenderer.sendSync("get-library-recentlyAdded"),
// playlists: ipcRenderer.sendSync("get-library-playlists") // playlists: ipcRenderer.sendSync("get-library-playlists")
},
pageState: {
recentlyAdded: {
loaded: false,
nextUrl: null,
items: [],
size: "normal"
},
settings: {
currentTabIndex: 0,
fullscreen: false
}
},
artwork: {
playerLCD: ""
}
}, },
mutations: { pageState: {
resetRecentlyAdded(state) { recentlyAdded: {
state.pageState.recentlyAdded.loaded = false; loaded: false,
state.pageState.recentlyAdded.nextUrl = null; nextUrl: null,
state.pageState.recentlyAdded.items = []; items: [],
}, size: "normal",
setLCDArtwork(state, artwork) { },
state.artwork.playerLCD = artwork 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 };

View file

@ -1,194 +1,230 @@
const wsapi = { const wsapi = {
cache: {playParams: {id: 0}, status: null, remainingTime: 0}, cache: { playParams: { id: 0 }, status: null, remainingTime: 0 },
playbackCache: {status: null, time: Date.now()}, playbackCache: { status: null, time: Date.now() },
async v3(encoded = "") { async v3(encoded = "") {
let decoded = atob(encoded); let decoded = atob(encoded);
let json = JSON.parse(decoded); let json = JSON.parse(decoded);
console.log(json) console.log(json);
let response = await (await MusicKit.getInstance().api.v3.music(json.route, json.body, json.options)) let response = await await MusicKit.getInstance().api.v3.music(json.route, json.body, json.options);
let ret = response.data let ret = response.data;
return JSON.stringify(ret) return JSON.stringify(ret);
}, },
search(term, limit) { search(term, limit) {
MusicKit.getInstance().api.search(term, {limit: limit, types: 'songs,artists,albums,playlists'}).then((results)=>{ MusicKit.getInstance()
ipcRenderer.send('wsapi-returnSearch', JSON.stringify(results)) .api.search(term, {
}) limit: limit,
}, types: "songs,artists,albums,playlists",
searchLibrary(term, limit) { })
MusicKit.getInstance().api.library.search(term, {limit: limit, types: 'library-songs,library-artists,library-albums,library-playlists'}).then((results)=>{ .then((results) => {
ipcRenderer.send('wsapi-returnSearchLibrary', JSON.stringify(results)) ipcRenderer.send("wsapi-returnSearch", JSON.stringify(results));
}) });
}, },
getAttributes: function () { searchLibrary(term, limit) {
const mk = MusicKit.getInstance(); MusicKit.getInstance()
const nowPlayingItem = mk.nowPlayingItem; .api.library.search(term, {
const isPlayingExport = mk.isPlaying; limit: limit,
const remainingTimeExport = mk.currentPlaybackTimeRemaining; types: "library-songs,library-artists,library-albums,library-playlists",
const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {}); })
.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.status = isPlayingExport ? isPlayingExport : false;
attributes.name = attributes.name ? attributes.name : 'No Title Found'; attributes.name = attributes.name ? attributes.name : "No Title Found";
attributes.artwork = attributes.artwork ? attributes.artwork : {url: ''}; attributes.artwork = attributes.artwork ? attributes.artwork : { url: "" };
attributes.artwork.url = attributes.artwork.url ? attributes.artwork.url : ''; attributes.artwork.url = attributes.artwork.url ? attributes.artwork.url : "";
attributes.playParams = attributes.playParams ? attributes.playParams : {id: 'no-id-found'}; attributes.playParams = attributes.playParams ? attributes.playParams : { id: "no-id-found" };
attributes.playParams.id = attributes.playParams.id ? 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.albumName = attributes.albumName ? attributes.albumName : "";
attributes.artistName = attributes.artistName ? attributes.artistName : ''; attributes.artistName = attributes.artistName ? attributes.artistName : "";
attributes.genreNames = attributes.genreNames ? attributes.genreNames : []; attributes.genreNames = attributes.genreNames ? attributes.genreNames : [];
attributes.remainingTime = remainingTimeExport ? (remainingTimeExport * 1000) : 0; attributes.remainingTime = remainingTimeExport ? remainingTimeExport * 1000 : 0;
attributes.durationInMillis = attributes.durationInMillis ? attributes.durationInMillis : 0; attributes.durationInMillis = attributes.durationInMillis ? attributes.durationInMillis : 0;
attributes.startTime = Date.now(); attributes.startTime = Date.now();
attributes.endTime = attributes.endTime ? attributes.endTime : Date.now(); attributes.endTime = attributes.endTime ? attributes.endTime : Date.now();
attributes.volume = mk.volume; attributes.volume = mk.volume;
attributes.shuffleMode = mk.shuffleMode; attributes.shuffleMode = mk.shuffleMode;
attributes.repeatMode = mk.repeatMode; attributes.repeatMode = mk.repeatMode;
attributes.autoplayEnabled = mk.autoplayEnabled; attributes.autoplayEnabled = mk.autoplayEnabled;
return attributes return attributes;
}, },
moveQueueItem(oldPosition, newPosition) { moveQueueItem(oldPosition, newPosition) {
MusicKit.getInstance().queue._queueItems.splice(newPosition,0,MusicKit.getInstance().queue._queueItems.splice(oldPosition,1)[0]) MusicKit.getInstance().queue._queueItems.splice(newPosition, 0, MusicKit.getInstance().queue._queueItems.splice(oldPosition, 1)[0]);
MusicKit.getInstance().queue._reindex() MusicKit.getInstance().queue._reindex();
}, },
setAutoplay(value) { setAutoplay(value) {
MusicKit.getInstance().autoplayEnabled = value MusicKit.getInstance().autoplayEnabled = value;
}, },
returnDynamic(data, type) { returnDynamic(data, type) {
ipcRenderer.send('wsapi-returnDynamic', JSON.stringify(data), type) ipcRenderer.send("wsapi-returnDynamic", JSON.stringify(data), type);
}, },
musickitApi(method, id, params, library = false) { musickitApi(method, id, params, library = false) {
if (library) { if (library) {
MusicKit.getInstance().api.library[method](id, params).then((results)=>{ MusicKit.getInstance()
ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method) .api.library[method](id, params)
}) .then((results) => {
} else { ipcRenderer.send("wsapi-returnMusicKitApi", JSON.stringify(results), method);
MusicKit.getInstance().api[method](id, params).then((results)=>{ });
ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method) } else {
}) MusicKit.getInstance()
} .api[method](id, params)
}, .then((results) => {
getPlaybackState () { ipcRenderer.send("wsapi-returnMusicKitApi", JSON.stringify(results), method);
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);
}
});
}
} }
} },
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

View file

@ -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} didnt 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} didnt 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 //# sourceMappingURL=sw.js.map

View file

@ -1,3 +1,3 @@
#app { #app {
--color1: #111; --color1: #111;
} }

View file

@ -1 +1 @@
// Default theme // Default theme

View file

@ -1,3 +1,3 @@
body.notransparency::before { body.notransparency::before {
display: block; display: block;
} }

View file

@ -1,41 +1,40 @@
&:not(.modular-fs) { &: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 { .app-drawer {
border-radius: 0px; margin-right: 0px;
top : 0; position: absolute;
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-enter-active,
.drawertransition-leave-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-enter,
.drawertransition-leave-to { .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;
}
}
}

View file

@ -1,141 +1,139 @@
body { body {
--ciderShadow-Generic : var(--mediaItemShadow); --ciderShadow-Generic: var(--mediaItemShadow);
--mediaItemShadow-Shadow : var(--mediaItemShadow); --mediaItemShadow-Shadow: var(--mediaItemShadow);
--mediaItemShadow-ShadowSubtle: var(--mediaItemShadow); --mediaItemShadow-ShadowSubtle: var(--mediaItemShadow);
} }
.bg-artwork-container { .bg-artwork-container {
display : none; display: none;
animation: none !important; animation: none !important;
.bg-artwork { .bg-artwork {
animation: none !important; animation: none !important;
} }
} }
.app-chrome:not(.chrome-bottom) { .app-chrome:not(.chrome-bottom) {
backdrop-filter: unset; backdrop-filter: unset;
background-color: var(--baseColor); background-color: var(--baseColor);
} }
.menu-panel .menu-panel-body { .menu-panel .menu-panel-body {
background: rgb(30 30 30); background: rgb(30 30 30);
} }
.menu-panel .menu-panel-body .menu-option::before { .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 { #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 { .playback-button:before,
transition: unset!important; .playback-button--small:before {
transition: unset !important;
} }
.floating-header { .floating-header {
backdrop-filter: unset!important; backdrop-filter: unset !important;
background: rgb(0 0 0 / 80%)!important; background: rgb(0 0 0 / 80%) !important;
} }
.replaycard-enter-active, .replaycard-enter-active,
.replaycard-leave-active { .replaycard-leave-active {
transition: unset; transition: unset;
} }
.replaycard-enter, .replaycard-enter,
.replaycard-leave-to { .replaycard-leave-to {
opacity : 0; opacity: 0;
transform: translateY(20px); transform: translateY(20px);
} }
.modal-enter-active, .modal-enter-active,
.modal-leave-active { .modal-leave-active {
transition: unset; transition: unset;
} }
.modal-enter, .modal-enter,
.modal-leave-to { .modal-leave-to {
opacity : 0; opacity: 0;
transform: scale(1.10); transform: scale(1.1);
} }
.wpfade-enter-active, .wpfade-enter-active,
.wpfade-leave-active { .wpfade-leave-active {
transition: opacity .1s var(--appleEase); transition: opacity 0.1s var(--appleEase);
} }
.wpfade-enter, .wpfade-enter,
.wpfade-leave-to { .wpfade-leave-to {
opacity: 0; opacity: 0;
} }
.wpfade_transform-enter-active, .wpfade_transform-enter-active,
.wpfade_transform-leave-active { .wpfade_transform-leave-active {
transition : unset; transition: unset;
will-change: unset; will-change: unset;
} }
.wpfade_transform-enter { .wpfade_transform-enter {
opacity : 0; opacity: 0;
transform : unset; transform: unset;
will-change: unset; will-change: unset;
} }
.wpfade_transform-leave-to { .wpfade_transform-leave-to {
opacity : 0; opacity: 0;
transform : unset; transform: unset;
will-change: unset; will-change: unset;
} }
.wpfade_transform_backwards-enter-active, .wpfade_transform_backwards-enter-active,
.wpfade_transform_backwards-leave-active { .wpfade_transform_backwards-leave-active {
transition: unset; transition: unset;
} }
.wpfade_transform_backwards-enter { .wpfade_transform_backwards-enter {
opacity : 0; opacity: 0;
transform : unset; transform: unset;
will-change: unset; will-change: unset;
} }
.wpfade_transform_backwards-leave-to { .wpfade_transform_backwards-leave-to {
opacity : 0; opacity: 0;
transform : unset; transform: unset;
will-change: unset; will-change: unset;
} }
.fabfade-enter-active, .fabfade-enter-active,
.fabfade-leave-active { .fabfade-leave-active {
transition: unset; transition: unset;
} }
.fabfade-enter, .fabfade-enter,
.fabfade-leave-to { .fabfade-leave-to {
opacity : 0; opacity: 0;
transform: scale(0.5); transform: scale(0.5);
} }
.fsModeSwitch-enter-active, .fsModeSwitch-enter-active,
.fsModeSwitch-leave-active { .fsModeSwitch-leave-active {
transition: unset; transition: unset;
} }
.fsModeSwitch-enter, .fsModeSwitch-enter,
.fsModeSwitch-leave-to { .fsModeSwitch-leave-to {
transform: scale(1.10); transform: scale(1.1);
opacity : 0; opacity: 0;
} }
.drawertransition-enter-active, .drawertransition-enter-active,
.drawertransition-leave-active { .drawertransition-leave-active {
transition: unset; transition: unset;
} }
.drawertransition-enter, .drawertransition-enter,
.drawertransition-leave-to { .drawertransition-leave-to {
right: -300px; right: -300px;
} }

View file

@ -1,49 +1,47 @@
@panelColorsFallback: rgb(30 30 30); @panelColorsFallback: rgb(30 30 30);
@panelColors : rgb(30 30 30 / 45%); @panelColors: rgb(30 30 30 / 45%);
.menu-panel { .menu-panel {
.menu-panel-body { .menu-panel-body {
background-color: @panelColors; background-color: @panelColors;
backdrop-filter : blur(32px) saturate(180%); backdrop-filter: blur(32px) saturate(180%);
&.menu-panel-body-down { &.menu-panel-body-down {
animation: menuInDown .10s var(--appleEase); animation: menuInDown 0.1s var(--appleEase);
}
&.menu-panel-body-up {
animation: menuInUp .10s var(--appleEase);
}
} }
@keyframes menuInUp { &.menu-panel-body-up {
0% { animation: menuInUp 0.1s var(--appleEase);
opacity : 0; }
transform : translateY(-10px) translate3d(0,0,0); }
background: @panelColorsFallback;
}
100% { @keyframes menuInUp {
opacity : 1; 0% {
transform : translateY(0); opacity: 0;
background: @panelColors; transform: translateY(-10px) translate3d(0, 0, 0);
} background: @panelColorsFallback;
} }
@keyframes menuInDown { 100% {
0% { opacity: 1;
opacity : 0; transform: translateY(0);
transform : translateY(10px) translate3d(0,0,0); background: @panelColors;
background: @panelColorsFallback; }
} }
100% { @keyframes menuInDown {
opacity : 1; 0% {
transform : translateY(0); opacity: 0;
background: @panelColors; transform: translateY(10px) translate3d(0, 0, 0);
} background: @panelColorsFallback;
} }
100% {
opacity: 1;
transform: translateY(0);
background: @panelColors;
}
}
} }
.cd-mediaitem-square { .cd-mediaitem-square {
@ -55,47 +53,46 @@
} }
.cd-mediaitem-square:not(.mediaitem-card) { .cd-mediaitem-square:not(.mediaitem-card) {
transition : transform .2s var(--appleEase); transition: transform 0.2s var(--appleEase);
transition-delay: .1s; 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, .artwork-container {
.info-rect { transform: scale(0.962) translateZ(0);
transition : transform .22s var(--appleEase); transition: transform 0.1s var(--appleEase);
transition-delay: .05s; transition-delay: 0s;
} transform-origin: center;
}
&:hover {
.artwork-container { .artwork-container {
transform : scale(0.962) translateZ(0); transform: scale(1);
transition : transform .1s var(--appleEase); transition: transform 0.1s var(--appleEase);
transition-delay: 0s; transition-delay: 0s;
transform-origin: center; transform-origin: center;
} }
&:hover { .info-rect {
.artwork-container { z-index: 1;
transform : scale(1.0); transition: transform 0.1s var(--appleEase);
transition : transform .1s var(--appleEase); transition-delay: 0s;
transition-delay: 0s; transform: translateY(8px) translate3d(0, 0, 0);
transform-origin: center;
}
.info-rect {
z-index : 1;
transition : transform .1s var(--appleEase);
transition-delay: 0s;
transform : translateY(8px) translate3d(0,0,0);
}
} }
}
&:active { &:active {
}
}
} }
.wpfade_transform-enter-active, .wpfade_transform-enter-active,
@ -107,16 +104,15 @@
.wpfade_transform-enter { .wpfade_transform-enter {
opacity: 0; opacity: 0;
transform: translateX(50%) translate3d(0,0,0); transform: translateX(50%) translate3d(0, 0, 0);
will-change: opacity, transform; will-change: opacity, transform;
} }
.wpfade_transform-leave-to { .wpfade_transform-leave-to {
opacity: 0; opacity: 0;
transform: translateX(-50%) translate3d(0,0,0); transform: translateX(-50%) translate3d(0, 0, 0);
will-change: opacity, transform; will-change: opacity, transform;
} }
.wpfade_transform_backwards-enter-active, .wpfade_transform_backwards-enter-active,
.wpfade_transform_backwards-leave-active { .wpfade_transform_backwards-leave-active {
--transitionTime: 0.2s; --transitionTime: 0.2s;
@ -125,11 +121,11 @@
.wpfade_transform_backwards-enter { .wpfade_transform_backwards-enter {
opacity: 0; opacity: 0;
transform: translateX(-50%) translate3d(0,0,0); transform: translateX(-50%) translate3d(0, 0, 0);
will-change: opacity, transform; will-change: opacity, transform;
} }
.wpfade_transform_backwards-leave-to { .wpfade_transform_backwards-leave-to {
opacity: 0; opacity: 0;
transform: translateX(50%) translate3d(0,0,0); transform: translateX(50%) translate3d(0, 0, 0);
will-change: opacity, transform; will-change: opacity, transform;
} }

View file

@ -1,180 +1,193 @@
// Apple Music Listen Now Page // Apple Music Listen Now Page
// URL : https://amp-api.music.apple.com/v1/me/recommendations?timezone=+00:00 // URL : https://amp-api.music.apple.com/v1/me/recommendations?timezone=+00:00
// &with=friendsMix,library,social&art[social-profiles:url]=c // &with=friendsMix,library,social&art[social-profiles:url]=c
// &name=listen-now&art[url]=c,f&omit[resource]=autos // &name=listen-now&art[url]=c,f&omit[resource]=autos
// &relate[editorial-items]=contents // &relate[editorial-items]=contents
// &extend=editorialCard,editorialVideo // &extend=editorialCard,editorialVideo
// &extend[albums]=artistUrl // &extend[albums]=artistUrl
// &extend[library-albums]=artistUrl // &extend[library-albums]=artistUrl
// &extend[playlists]=artistNames,editorialArtwork // &extend[playlists]=artistNames,editorialArtwork
// &extend[library-playlists]=artistNames,editorialArtwork // &extend[library-playlists]=artistNames,editorialArtwork
// &extend[social-profiles]=topGenreNames&include[albums]=artists // &extend[social-profiles]=topGenreNames&include[albums]=artists
// &include[songs]=artists&include[music-videos]=artists // &include[songs]=artists&include[music-videos]=artists
// &fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url // &fields[albums]=artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url
// &fields[artists]=name,url&extend[stations]=airDate,supportsAirTimeUpdates&meta[stations]=inflectionPoints // &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 // &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 // &l=en-gb&platform=web
await app.mk.api.personalRecommendations("", await app.mk.api.personalRecommendations(
{ "",
name: "listen-now", {
with: "friendsMix,library,social", name: "listen-now",
"art[social-profiles:url]":"c", with: "friendsMix,library,social",
"art[url]": "c,f", "art[social-profiles:url]": "c",
"omit[resource]": "autos", "art[url]": "c,f",
"relate[editorial-items]": "contents", "omit[resource]": "autos",
extend: ["editorialCard", "editorialVideo"], "relate[editorial-items]": "contents",
"extend[albums]": ["artistUrl"], extend: ["editorialCard", "editorialVideo"],
"extend[library-albums]": ["artistUrl"], "extend[albums]": ["artistUrl"],
"extend[playlists]": ["artistNames", "editorialArtwork"], "extend[library-albums]": ["artistUrl"],
"extend[library-playlists]": ["artistNames", "editorialArtwork"], "extend[playlists]": ["artistNames", "editorialArtwork"],
"extend[social-profiles]": "topGenreNames", "extend[library-playlists]": ["artistNames", "editorialArtwork"],
"include[albums]": "artists", "extend[social-profiles]": "topGenreNames",
"include[songs]": "artists", "include[albums]": "artists",
"include[music-videos]": "artists", "include[songs]": "artists",
"fields[albums]": ["artistName", "artistUrl", "artwork", "contentRating", "editorialArtwork", "editorialVideo", "name", "playParams", "releaseDate", "url"], "include[music-videos]": "artists",
"fields[artists]": ["name", "url"], "fields[albums]": ["artistName", "artistUrl", "artwork", "contentRating", "editorialArtwork", "editorialVideo", "name", "playParams", "releaseDate", "url"],
"extend[stations]": ["airDate", "supportsAirTimeUpdates"], "fields[artists]": ["name", "url"],
"meta[stations]": "inflectionPoints", "extend[stations]": ["airDate", "supportsAirTimeUpdates"],
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", "meta[stations]": "inflectionPoints",
l:"en-gb", 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",
platform:"web" l: "en-gb",
}, platform: "web",
{ },
includeResponseMeta: !0, {
reload: !0 includeResponseMeta: !0,
}); reload: !0,
}
);
// Browse page // Browse page
await app.mk.api.groupings("", await app.mk.api.groupings("", {
{ platform: "web",
platform: "web", name: "music",
name: "music", l: "en-gb",
l: "en-gb", "omit[resource:artists]": "relationships",
"omit[resource:artists]": "relationships", "include[albums]": "artists",
"include[albums]": "artists", "include[songs]": "artists",
"include[songs]": "artists", "include[music-videos]": "artists",
"include[music-videos]": "artists", extend: "editorialArtwork,artistUrl",
extend: "editorialArtwork,artistUrl", "fields[artists]": "name,url,artwork,editorialArtwork,genreNames,editorialNotes",
"fields[artists]": "name,url,artwork,editorialArtwork,genreNames,editorialNotes", "art[url]": "f",
"art[url]": "f" });
});
// Radio page // Radio page
await app.mk.api.recentRadioStations("", await app.mk.api.recentRadioStations("", {
{l: "en-gb", l: "en-gb",
"platform": "web", platform: "web",
"art[url]": "f"}); "art[url]": "f",
});
// Recently Added // Recently Added
await app.mk.api.library.recentlyAdded({ await app.mk.api.library.recentlyAdded(
"platform": "web", {
platform: "web",
include: { include: {
"library-albums": ["artists"], "library-albums": ["artists"],
"library-artists": ["catalog"] "library-artists": ["catalog"],
}, },
fields: { fields: {
artists: ["url"], artists: ["url"],
albums: "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url" albums: "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url",
}, },
includeOnly: ["catalog", "artists"], includeOnly: ["catalog", "artists"],
limit: 25 limit: 25,
}, { },
{
reload: !0, reload: !0,
includePagination: !0 includePagination: !0,
}) }
);
// Songs // Songs
await app.mk.api.library.songs({limit: 100}).then((data)=>{ await app.mk.api.library.songs({ limit: 100 }).then((data) => {
console.log(data) console.log(data);
}) });
// Artists // Artists
await app.mk.api.library.artists({limit: 100}).then((data)=>{ await app.mk.api.library.artists({ limit: 100 }).then((data) => {
console.log(data) console.log(data);
}) });
// Artists // Artists
await app.mk.api.library.albums({limit: 100}).then((data)=>{ await app.mk.api.library.albums({ limit: 100 }).then((data) => {
console.log(data) console.log(data);
}) });
// Albums // Albums
// does not like limit = 100 for some reason // does not like limit = 100 for some reason
await app.mk.api.library.albums({limit: 50}).then((data)=>{ await app.mk.api.library.albums({ limit: 50 }).then((data) => {
console.log(data) console.log(data);
}) });
// Made For You // Made For You
app.mk.api.recommendations("",{extend: "editorialArtwork,artistUrl"}) app.mk.api.recommendations("", { extend: "editorialArtwork,artistUrl" });
// Library with library length // Library with library length
await app.mk.api.library.songs("", {limit: 100}, {includeResponseMeta: !0}).then((data)=>{ await app.mk.api.library.songs("", { limit: 100 }, { includeResponseMeta: !0 }).then((data) => {
console.log(data) console.log(data);
}) });
// Artist View Top Songs // 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 // Artist Page Data
app.mkapi("artists", false, "412778295", { app
"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", .mkapi(
"extend": "artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero", "artists",
"extend[playlists]": "trackCount", false,
"omit[resource:songs]": "relationships", "412778295",
"fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url,trackCount", {
"limit[artists:top-songs]": 20, 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",
"art[url]": "f" extend: "artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero",
}, {includeResponseMeta: !0}).then((data)=>{ "extend[playlists]": "trackCount",
console.log(data) "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 // download entire library
var library = [] var library = [];
var downloaded = null; var downloaded = null;
function downloadChunk () { function downloadChunk() {
if (downloaded == null) { if (downloaded == null) {
app.mk.api.library.songs("", {limit: 100}, {includeResponseMeta: !0}).then((response)=>{ app.mk.api.library.songs("", { limit: 100 }, { includeResponseMeta: !0 }).then((response) => {
processChunk(response) processChunk(response);
}) });
} else { } else {
downloaded.next("", {limit: 100}, {includeResponseMeta: !0}).then((response)=>{ downloaded.next("", { limit: 100 }, { includeResponseMeta: !0 }).then((response) => {
processChunk(response) processChunk(response);
}) });
} }
} }
function processChunk (response) { function processChunk(response) {
downloaded = response downloaded = response;
library = library.concat(downloaded.data) library = library.concat(downloaded.data);
if (downloaded.meta.total > library.length) { if (downloaded.meta.total > library.length) {
console.log(`downloading next chunk - ${library.length} songs so far`) console.log(`downloading next chunk - ${library.length} songs so far`);
downloadChunk() downloadChunk();
} else { } else {
console.log(library) console.log(library);
} }
} }
//Some Available Functions from MusicKit //Some Available Functions from MusicKit
// recentPlayed() -> recently played songs ? // recentPlayed() -> recently played songs ?
// create Artist / Song/ Album stations: // create Artist / Song/ Album stations:
app.mk.setStationQueue({artist:"1258279972"}) 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({ 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 // Sorting Playlists, send an array of tracks in the format below
// playlist must be fully recursively downloaded first before sorting // playlist must be fully recursively downloaded first before sorting
app.mk.api.library.putPlaylistTracklisting(app.showingPlaylist.attributes.playParams.id, [ app.mk.api.library.putPlaylistTracklisting(app.showingPlaylist.attributes.playParams.id, [
{ {
"id": relationships.tracks.data[X].id, id: relationships.tracks.data[X].id,
"type": relationships.tracks.data[X].type type: relationships.tracks.data[X].type,
}, },
{ {
"id": relationships.tracks.data[X].id, id: relationships.tracks.data[X].id,
"type": relationships.tracks.data[X].type type: relationships.tracks.data[X].type,
}, },
{ {
"id": relationships.tracks.data[X].id, id: relationships.tracks.data[X].id,
"type": relationships.tracks.data[X].type type: relationships.tracks.data[X].type,
}, },
]) ]);

View file

@ -1,136 +1,136 @@
<div class="app-navigation" v-cloak> <div class="app-navigation" v-cloak>
<transition name="wpfade"> <transition name="wpfade">
<div class="usermenu-container" v-if="chrome.menuOpened"> <div class="usermenu-container" v-if="chrome.menuOpened">
<div class="usermenu-body"> <div class="usermenu-body">
<button <button
class="app-sidebar-button" class="app-sidebar-button"
style="width: 100%" style="width: 100%"
@click="appRoute('apple-account-settings')" @click="appRoute('apple-account-settings')"
> >
<img <img
class="sidebar-user-icon" class="sidebar-user-icon"
loading="lazy" loading="lazy"
:src="getMediaItemArtwork(chrome.hideUserInfo ? './assets/logocut.png' : (chrome.userinfo.attributes['artwork'] ? chrome.userinfo.attributes['artwork']['url'] : ''), 26)" :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"> <div class="sidebar-user-text" v-if="!chrome.hideUserInfo">
<template v-if="chrome.userinfo.id || mk.isAuthorized"> <template v-if="chrome.userinfo.id || mk.isAuthorized">
<div class="fullname text-overflow-elipsis"> <div class="fullname text-overflow-elipsis">
{{ {{
chrome.userinfo != null && chrome.userinfo != null &&
chrome.userinfo.attributes != null chrome.userinfo.attributes != null
? chrome.userinfo.attributes.name ?? "" ? chrome.userinfo.attributes.name ?? ""
: "" : ""
}} }}
</div> </div>
<div class="handle-text text-overflow-elipsis"> <div class="handle-text text-overflow-elipsis">
{{ {{
chrome.userinfo != null && chrome.userinfo != null &&
chrome.userinfo.attributes != null chrome.userinfo.attributes != null
? chrome.userinfo.attributes.handle ?? "" ? chrome.userinfo.attributes.handle ?? ""
: "" : ""
}} }}
</div> </div>
</template> </template>
<template v-else> <template v-else>
<div @click="mk.authorize()"> <div @click="mk.authorize()">
{{ $root.getLz("term.login") }} {{ $root.getLz("term.login") }}
</div> </div>
</template> </template>
</div> </div>
<div class="sidebar-user-text" v-else> <div class="sidebar-user-text" v-else>
{{ $root.getLz("app.name") }} {{ $root.getLz("app.name") }}
</div> </div>
</button> </button>
<!-- Use 20px SVG for usermenu icon --> <!-- Use 20px SVG for usermenu icon -->
<button <button
class="usermenu-item" class="usermenu-item"
v-if="cfg.general.privateEnabled" v-if="cfg.general.privateEnabled"
@click="cfg.general.privateEnabled = false" @click="cfg.general.privateEnabled = false"
> >
<span class="usermenu-item-icon"> <span class="usermenu-item-icon">
<%- include("../svg/x.svg") %> <%- include("../svg/x.svg") %>
</span> </span>
<span class="usermenu-item-name">{{ <span class="usermenu-item-name">{{
$root.getLz("term.disablePrivateSession") $root.getLz("term.disablePrivateSession")
}}</span> }}</span>
</button> </button>
<button class="usermenu-item" @click="appRoute('remote-pair')"> <button class="usermenu-item" @click="appRoute('remote-pair')">
<span class="usermenu-item-icon"> <span class="usermenu-item-icon">
<%- include("../svg/smartphone.svg") %> <%- include("../svg/smartphone.svg") %>
</span> </span>
<span class="usermenu-item-name">{{ <span class="usermenu-item-name">{{
$root.getLz("action.showWebRemoteQR") $root.getLz("action.showWebRemoteQR")
}}</span> }}</span>
</button> </button>
<button <button
class="usermenu-item" class="usermenu-item"
@click="modals.castMenu = true" @click="modals.castMenu = true"
> >
<span class="usermenu-item-icon"> <span class="usermenu-item-icon">
<%- include("../svg/cast.svg") %> <%- include("../svg/cast.svg") %>
</span> </span>
<span class="usermenu-item-name">{{ <span class="usermenu-item-name">{{
$root.getLz("term.cast") $root.getLz("term.cast")
}}</span> }}</span>
</button> </button>
<button <button
class="usermenu-item" class="usermenu-item"
@click="modals.audioSettings = true" @click="modals.audioSettings = true"
> >
<span class="usermenu-item-icon"> <span class="usermenu-item-icon">
<%- include("../svg/headphones.svg") %> <%- include("../svg/headphones.svg") %>
</span> </span>
<span class="usermenu-item-name">{{ <span class="usermenu-item-name">{{
$root.getLz("term.audioSettings") $root.getLz("term.audioSettings")
}}</span> }}</span>
</button> </button>
<button <button
class="usermenu-item" class="usermenu-item"
v-if="pluginInstalled" v-if="pluginInstalled"
@click="modals.pluginMenu = true" @click="modals.pluginMenu = true"
> >
<span class="usermenu-item-icon"> <span class="usermenu-item-icon">
<%- include("../svg/grid.svg") %> <%- include("../svg/grid.svg") %>
</span> </span>
<span class="usermenu-item-name">{{ <span class="usermenu-item-name">{{
$root.getLz("term.plugin") $root.getLz("term.plugin")
}}</span> }}</span>
</button> </button>
<button class="usermenu-item" @click="appRoute('about')"> <button class="usermenu-item" @click="appRoute('about')">
<span class="usermenu-item-icon"> <span class="usermenu-item-icon">
<%- include("../svg/info.svg") %> <%- include("../svg/info.svg") %>
</span> </span>
<span class="usermenu-item-name">{{ <span class="usermenu-item-name">{{
$root.getLz("term.about") $root.getLz("term.about")
}}</span> }}</span>
</button> </button>
<button class="usermenu-item" @click="modals.settings = true"> <button class="usermenu-item" @click="modals.settings = true">
<span class="usermenu-item-icon"> <span class="usermenu-item-icon">
<%- include("../svg/settings.svg") %> <%- include("../svg/settings.svg") %>
</span> </span>
<span class="usermenu-item-name">{{ <span class="usermenu-item-name">{{
$root.getLz("term.settings") $root.getLz("term.settings")
}}</span> }}</span>
</button> </button>
<button class="usermenu-item" @click="unauthorize()"> <button class="usermenu-item" @click="unauthorize()">
<span class="usermenu-item-icon" style="right: 2.5px"> <span class="usermenu-item-icon" style="right: 2.5px">
<%- include("../svg/log-out.svg") %> <%- include("../svg/log-out.svg") %>
</span> </span>
<span class="usermenu-item-name">{{ <span class="usermenu-item-name">{{
$root.getLz("term.logout") $root.getLz("term.logout")
}}</span> }}</span>
</button> </button>
<button class="usermenu-item" @click="quit()"> <button class="usermenu-item" @click="quit()">
<span class="usermenu-item-icon" style="right: 2.5px"> <span class="usermenu-item-icon" style="right: 2.5px">
<%- include("../svg/x.svg") %> <%- include("../svg/x.svg") %>
</span> </span>
<span class="usermenu-item-name">{{ <span class="usermenu-item-name">{{
$root.getLz("term.quit") $root.getLz("term.quit")
}}</span> }}</span>
</button> </button>
</div>
</div> </div>
</div>
</transition> </transition>
<transition name="sidebartransition"> <transition name="sidebartransition">
<cider-app-sidebar v-if="!chrome.sidebarCollapsed"></cider-app-sidebar> <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"> v-if="drawer.open && drawer.panel == 'lyrics' && lyrics && lyrics != [] && lyrics.length > 0">
<div class="bgArtworkMaterial"> <div class="bgArtworkMaterial">
<div class="bg-artwork-container"> <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 a"
<img v-if="(cfg.visual.bg_artwork_rotation && animateBackground)" class="bg-artwork b" :src="$store.state.artwork.playerLCD"> :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 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>
</div> </div>
<lyrics-view v-if="drawer.panel == 'lyrics'" :time="mk.currentPlaybackTime - lyricOffset" :lyrics="lyrics" <lyrics-view v-if="drawer.panel == 'lyrics'" :time="mk.currentPlaybackTime - lyricOffset" :lyrics="lyrics"

View file

@ -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--left">
<div class="app-chrome-item playback-controls"> <div class="app-chrome-item playback-controls">
<template v-if="mkReady()"> <template v-if="mkReady()">
@ -16,15 +17,23 @@
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork> <mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
</div> </div>
<div class="song-name">{{ mk.nowPlayingItem["attributes"]["name"] }}</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`)"> <div class="song-album" @click="getNowPlayingItemDetailed(`album`)">
{{(mk.nowPlayingItem["attributes"]["albumName"]) ? {{(mk.nowPlayingItem["attributes"]["albumName"]) ?
(mk.nowPlayingItem["attributes"]["albumName"]) : "" }} (mk.nowPlayingItem["attributes"]["albumName"]) : "" }}
</div> </div>
<hr> <hr>
<div class="btn-group" style="width:100%;"> <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%;"
<button class="md-btn md-btn-small" style="width: 100%;" @click="drawer.open = false; fullscreen(true)">{{ $root.getLz("term.fullscreenView") }}</button> @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>
</div> </div>
</b-popover> </b-popover>
@ -39,20 +48,26 @@
<div class="song-artist" @click="getNowPlayingItemDetailed(`artist`)"> <div class="song-artist" @click="getNowPlayingItemDetailed(`artist`)">
{{ mk.nowPlayingItem["attributes"]["artistName"] }} {{ mk.nowPlayingItem["attributes"]["artistName"] }}
</div> </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"]) ?
(mk.nowPlayingItem["attributes"]["albumName"]) : "" }} (mk.nowPlayingItem["attributes"]["albumName"]) : "" }}
</div> </div>
<div class="chrome-icon-container"> <div class="chrome-icon-container">
<div class="audio-type private-icon" v-if="cfg.general.privateEnabled === true"></div> <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" <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 :title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization') + ' (' + getProfileLz('CTS', cfg.audio.maikiwiAudio.spatialProfile) + ')'"
v-b-tooltip.hover
></div> ></div>
<div class="audio-type lossless-icon" v-if="(mk.nowPlayingItem?.localFilesMetadata?.lossless ?? false) === true" <div class="audio-type lossless-icon"
:title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container" v-b-tooltip.hover v-if="(mk.nowPlayingItem?.localFilesMetadata?.lossless ?? false) === true"
></div> :title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container"
<div class="audio-type ppe-icon" v-if="mk.nowPlayingItem.localFilesMetadata == null && cfg.audio.maikiwiAudio.ciderPPE === true" v-b-tooltip.hover
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')" 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> </div>
</div> </div>
@ -75,7 +90,7 @@
</div> </div>
<div class="playback-info"> <div class="playback-info">
<div class="song-name"> <div class="song-name">
</div> </div>
</div> </div>
@ -86,8 +101,9 @@
</div> </div>
<div class="app-chrome--center"> <div class="app-chrome--center">
<div class="app-chrome-playback-duration-bottom"> <div class="app-chrome-playback-duration-bottom">
<b-row v-if="mkReady()"> <b-row v-if="mkReady()">
<b-col sm="auto" v-if="!mk.nowPlayingItem?.isLiveRadioStation">{{ convertTime(getSongProgress()) }}</b-col> <b-col sm="auto" v-if="!mk.nowPlayingItem?.isLiveRadioStation">{{ convertTime(getSongProgress()) }}
</b-col>
<b-col sm="auto" v-else>--:--</b-col> <b-col sm="auto" v-else>--:--</b-col>
<b-col> <b-col>
<input type="range" step="0.01" min="0" :style="progressBarStyle()" <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);" @touchend="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);"
:max="mk.currentPlaybackDuration" :value="getSongProgress()"> :max="mk.currentPlaybackDuration" :value="getSongProgress()">
</b-col> </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-col sm="auto" v-else>{{ getLz("term.live") }}</b-col>
</b-row> </b-row>
</div> </div>
<div class="app-chrome-playback-controls"> <div class="app-chrome-playback-controls">
<div class="app-chrome-item"> <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')" @click="mk.shuffleMode = 1" :title="$root.getLz('term.enableShuffle')"
v-b-tooltip.hover></button> v-b-tooltip.hover></button>
<button class="playback-button--small shuffle active" v-else :class="isDisabled() && 'disabled'" <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> :title="$root.getLz('term.previous')" v-b-tooltip.hover></button>
</div> </div>
<div class="app-chrome-item"> <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> :title="$root.getLz('term.stop')" v-b-tooltip.hover></button>
<button class="playback-button pause" @click="mk.pause()" v-else-if="mk.isPlaying" <button class="playback-button pause" @click="mk.pause()" v-else-if="mk.isPlaying"
:title="$root.getLz('term.pause')" v-b-tooltip.hover></button> :title="$root.getLz('term.pause')" v-b-tooltip.hover></button>
@ -126,13 +146,16 @@
:title="$root.getLz('term.next')" v-b-tooltip.hover></button> :title="$root.getLz('term.next')" v-b-tooltip.hover></button>
</div> </div>
<div class="app-chrome-item"> <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')" @click="mk.repeatMode = 1" :title="$root.getLz('term.enableRepeatOne')"
v-b-tooltip.hover></button> 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-else-if="mk.repeatMode == 1" :title="$root.getLz('term.disableRepeatOne')"
v-b-tooltip.hover></button> 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-else-if="mk.repeatMode == 2" :title="$root.getLz('term.disableRepeat')"
v-b-tooltip.hover></button> v-b-tooltip.hover></button>
</div> </div>
@ -149,27 +172,27 @@
v-b-tooltip.hover :title="formatVolumeTooltip()"> v-b-tooltip.hover :title="formatVolumeTooltip()">
</div> </div>
<div class="app-chrome-item generic"> <div class="app-chrome-item generic">
<button class="playback-button--small cast" <button class="playback-button--small cast"
:title="$root.getLz('term.cast')" :title="$root.getLz('term.cast')"
v-b-tooltip.hover v-b-tooltip.hover
@click="modals.castMenu = true"></button> @click="modals.castMenu = true"></button>
</div> </div>
<div class="app-chrome-item generic"> <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')" :title="$root.getLz('term.queue')"
v-b-tooltip.hover v-b-tooltip.hover
@click="invokeDrawer('queue')"></button> @click="invokeDrawer('queue')"></button>
</div> </div>
<div class="app-chrome-item generic"> <div class="app-chrome-item generic">
<template v-if="lyrics && lyrics != [] && lyrics.length > 0"> <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')" :title="$root.getLz('term.lyrics')"
v-b-tooltip.hover v-b-tooltip.hover
:class="{'active': drawer.panel == 'lyrics'}" :class="{'active': drawer.panel == 'lyrics'}"
@click="invokeDrawer('lyrics')"></button> @click="invokeDrawer('lyrics')"></button>
</template> </template>
<template v-else> <template v-else>
<button class="playback-button--small lyrics" <button class="playback-button--small lyrics"
:style="{'opacity': 0.3, 'pointer-events': 'none'}"></button> :style="{'opacity': 0.3, 'pointer-events': 'none'}"></button>
</template> </template>

View file

@ -1,286 +1,313 @@
<div class="app-chrome" :style="{'display': chrome.topChromeVisible ? '' : 'none'}"> <div class="app-chrome" :style="{'display': chrome.topChromeVisible ? '' : 'none'}">
<div class="app-chrome--left"> <div class="app-chrome--left">
<div class="app-chrome-item full-height" v-if="chrome.windowControlPosition == 'left' && !chrome.nativeControls"> <div class="app-chrome-item full-height"
<div class="window-controls-macos"> v-if="chrome.windowControlPosition == 'left' && !chrome.nativeControls">
<div class="close" @click="ipcRenderer.send('close')"></div> <div class="window-controls-macos">
<div class="minimize" @click="ipcRenderer.send('minimize')"></div> <div class="close" @click="ipcRenderer.send('close')"></div>
<div class="minmax restore" v-if="chrome.maximized" @click="ipcRenderer.send('maximize')"></div> <div class="minimize" @click="ipcRenderer.send('minimize')"></div>
<div class="minmax" v-else @click="ipcRenderer.send('maximize')"></div> <div class="minmax restore" v-if="chrome.maximized" @click="ipcRenderer.send('maximize')"></div>
</div> <div class="minmax" v-else @click="ipcRenderer.send('maximize')"></div>
</div> </div>
<div class="app-chrome-item full-height" v-else> </div>
<button class="app-mainmenu" @blur="mainMenuVisibility(false)" @click="mainMenuVisibility(true)" <div class="app-chrome-item full-height" v-else>
@contextmenu="mainMenuVisibility(true)" :class="{active: chrome.menuOpened}" <button class="app-mainmenu" @blur="mainMenuVisibility(false)" @click="mainMenuVisibility(true)"
:aria-label="$root.getLz('term.quickNav')"></button> @contextmenu="mainMenuVisibility(true)" :class="{active: chrome.menuOpened}"
</div> :aria-label="$root.getLz('term.quickNav')"></button>
<template v-if="getThemeDirective('appNavigation') != 'seperate'"> </div>
<div class="vdiv" v-if="getThemeDirective('windowLayout') == 'twopanel'"></div> <template v-if="getThemeDirective('appNavigation') != 'seperate'">
<div class="app-chrome-item"> <div class="vdiv" v-if="getThemeDirective('windowLayout') == 'twopanel'"></div>
<button class="playback-button navigation" @click="navigateBack()" :title="$root.getLz('term.navigateBack')" <div class="app-chrome-item">
v-b-tooltip.hover> <button class="playback-button navigation" @click="navigateBack()"
<svg-icon url="./views/svg/chevron-left.svg"></svg-icon> :title="$root.getLz('term.navigateBack')"
</button> v-b-tooltip.hover>
</div> <svg-icon url="./views/svg/chevron-left.svg"></svg-icon>
<div class="app-chrome-item"> </button>
<button class="playback-button navigation" @click="navigateForward()" </div>
:title="$root.getLz('term.navigateForward')" v-b-tooltip.hover> <div class="app-chrome-item">
<svg-icon url="./views/svg/chevron-right.svg"></svg-icon> <button class="playback-button navigation" @click="navigateForward()"
</button> :title="$root.getLz('term.navigateForward')" v-b-tooltip.hover>
</div> <svg-icon url="./views/svg/chevron-right.svg"></svg-icon>
<div class="app-chrome-item" v-if="getThemeDirective('windowLayout') == 'twopanel'"> </button>
<button class="playback-button collapseLibrary" v-b-tooltip.hover </div>
:title="chrome.sidebarCollapsed ? getLz('action.showLibrary') : getLz('action.hideLibrary')" <div class="app-chrome-item" v-if="getThemeDirective('windowLayout') == 'twopanel'">
@click="chrome.sidebarCollapsed = !chrome.sidebarCollapsed"> <button class="playback-button collapseLibrary" v-b-tooltip.hover
<transition name="fade"> :title="chrome.sidebarCollapsed ? getLz('action.showLibrary') : getLz('action.hideLibrary')"
<span v-if="chrome.sidebarCollapsed"></span> @click="chrome.sidebarCollapsed = !chrome.sidebarCollapsed">
</transition> <transition name="fade">
<transition name="fade"> <span v-if="chrome.sidebarCollapsed"></span>
<span v-if="!chrome.sidebarCollapsed"></span> </transition>
</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>
<button class="md-btn md-btn-small" style="width: 100%" @click="drawer.open = false; fullscreen(true)">
{{ $root.getLz("term.fullscreenView") }}
</button>
</div>
</div> </div>
</b-popover> <div class="vdiv display--large" v-if="getThemeDirective('windowLayout') != 'twopanel'"></div>
<div class="playback-info"> </template>
<div class="chrome-icon-container"> <template v-if="getThemeDirective('windowLayout') != 'twopanel'">
<div class="audio-type private-icon" v-if="cfg.general.privateEnabled === true"></div> <div class="app-chrome-item playback-control-buttons">
<div class="audio-type spatial-icon" v-if="cfg.audio.maikiwiAudio.spatial === true" <div class="app-chrome-item display--large">
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization') + ' (' + getProfileLz('CTS', cfg.audio.maikiwiAudio.spatialProfile) + ')'" v-b-tooltip.hover <button class="playback-button--small shuffle" v-if="mk.shuffleMode == 0"
></div> :class="isDisabled() && 'disabled'"
<div class="audio-type lossless-icon" v-if="(mk.nowPlayingItem?.localFilesMetadata?.lossless ?? false) === true" @click="mk.shuffleMode = 1" :title="$root.getLz('term.enableShuffle')"
:title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container" v-b-tooltip.hover v-b-tooltip.hover></button>
></div> <button class="playback-button--small shuffle active" v-else :class="isDisabled() && 'disabled'"
<div class="audio-type ppe-icon" v-if="mk.nowPlayingItem.localFilesMetadata == null && cfg.audio.maikiwiAudio.ciderPPE === true" @click="mk.shuffleMode = 0" :title="$root.getLz('term.disableShuffle')"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')" v-b-tooltip.hover v-b-tooltip.hover></button>
></div> </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>
<div class="info-rect"> </template>
<div class="song-name" </div>
:class="[isElementOverflowing('#app-main > div.app-chrome > div.app-chrome--center > div > div > div.playback-info > div.song-name') ? 'marquee' : '']"> <div class="app-chrome--center">
{{ mk.nowPlayingItem["attributes"]["name"] }} <div class="app-chrome-item playback-controls" v-if="getThemeDirective('windowLayout') != 'twopanel'">
<div class="explicit-icon" v-if="mk.nowPlayingItem['attributes']['contentRating'] == 'explicit'" <template v-if="mkReady()">
style="display: inline-block"></div> <div class="app-playback-controls" @mouseover="chrome.progresshover = true"
</div> @mouseleave="chrome.progresshover = false" @contextmenu="nowPlayingContextMenu">
<div class="song-artist-album"> <div class="artwork" id="artworkLCD">
<div class="song-artist-album-content" <mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
:class="[isElementOverflowing('#app-main > .app-chrome .app-chrome-item > .app-playback-controls > div >.song-artist-album > .song-artist-album-content') ? 'marquee' : '']" </div>
style=" <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; display: inline-block;
-webkit-box-orient: horizontal; -webkit-box-orient: horizontal;
white-space: nowrap; white-space: nowrap;
"> ">
<div class="item-navigate song-artist" style="display: inline-block" <div class="item-navigate song-artist" style="display: inline-block"
@click="getNowPlayingItemDetailed(`artist`)"> @click="getNowPlayingItemDetailed(`artist`)">
{{ mk.nowPlayingItem["attributes"]["artistName"] }} {{ mk.nowPlayingItem["attributes"]["artistName"] }}
</div> </div>
<div class="song-artist item-navigate" style="display: inline-block" <div class="song-artist item-navigate" style="display: inline-block"
@click="getNowPlayingItemDetailed('album')" @click="getNowPlayingItemDetailed('album')"
v-if="mk.nowPlayingItem['attributes']['albumName'] != ''"> v-if="mk.nowPlayingItem['attributes']['albumName'] != ''">
<div class="separator" style="display: inline-block"> <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> </div>
{{ <template v-if="mk.nowPlayingItem['attributes']['playParams']">
mk.nowPlayingItem["attributes"]["albumName"] <div class="actions">
? mk.nowPlayingItem["attributes"]["albumName"] <button class="lcdMenu" @click="nowPlayingContextMenu" :title="$root.getLz('term.more')"
: "" v-b-tooltip.hover>
}} <div class="svg-icon"></div>
</div> </button>
</div>
</template>
</div> </div>
</div> </template>
</div> <template v-else>
<div class="song-progress"> <div class="app-playback-controls">
<div class="song-duration" style="justify-content: space-between; height: 1px" <div class="artwork" id="artworkLCD" style="pointer-events: none;">
:style="[chrome.progresshover ? {'display': 'flex'} : {'display' : 'none'} ]"> <mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork>
<p style="width: auto">{{ convertTime(getSongProgress()) }}</p> </div>
<p style="width: auto"> <div class="playback-info">
{{ convertTime(mk.currentPlaybackDuration) }} <div class="info-rect">
</p>
</div>
<input type="range" step="0.01" min="0" :style="progressBarStyle()" </div>
@input="playerLCD.desiredDuration = $event.target.value;playerLCD.userInteraction = true" </div>
@mouseup="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);" </div>
@touchend="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);" </template>
: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>
</template> <div class="app-chrome-item" v-else>
<template v-else> <div class="top-nav-group">
<div class="app-playback-controls"> <sidebar-library-item :name="$root.getLz('home.title')" svg-icon="./assets/feather/home.svg"
<div class="artwork" id="artworkLCD" style="pointer-events: none;"> svg-icon-name="home" page="home">
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork> </sidebar-library-item>
</div> <sidebar-library-item :name="$root.getLz('term.listenNow')" svg-icon="./assets/feather/play-circle.svg"
<div class="playback-info"> svg-icon-name="listenNow"
<div class="info-rect"> 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> </div>
</template>
</div> </div>
<div class="app-chrome-item" v-else> <div class="app-chrome--right">
<div class="top-nav-group"> <template v-if="getThemeDirective('windowLayout') != 'twopanel'">
<sidebar-library-item :name="$root.getLz('home.title')" svg-icon="./assets/feather/home.svg" svg-icon-name="home" page="home"> <div class="app-chrome-item volume display--large">
</sidebar-library-item> <button class="volume-button--small volume" @click="muteButtonPressed()"
<sidebar-library-item :name="$root.getLz('term.listenNow')" svg-icon="./assets/feather/play-circle.svg" svg-icon-name="listenNow" :class="{'active': this.cfg.audio.volume == 0}"
page="listen_now"></sidebar-library-item> :title="cfg.audio.muted ? $root.getLz('term.unmute') : $root.getLz('term.mute')"
<sidebar-library-item :name="$root.getLz('term.browse')" svg-icon="./assets/feather/globe.svg" svg-icon-name="browse" page="browse"> v-b-tooltip.hover></button>
</sidebar-library-item> <input type="range" @wheel="volumeWheel" :step="cfg.audio.volumeStep" min="0" :max="cfg.audio.maxVolume"
<sidebar-library-item :name="$root.getLz('term.radio')" svg-icon="./assets/feather/radio.svg" svg-icon-name="radio" page="radio"> v-model="mk.volume" v-if="typeof mk.volume != 'undefined'" @change="checkMuteChange()"
</sidebar-library-item> v-b-tooltip.hover
</div> :title="formatVolumeTooltip()" />
</div> </div>
</div> <div class="app-chrome-item generic">
<div class="app-chrome--right"> <button class="playback-button--small cast" :title="$root.getLz('term.cast')"
<template v-if="getThemeDirective('windowLayout') != 'twopanel'"> @click="modals.castMenu = true"
<div class="app-chrome-item volume display--large"> v-b-tooltip.hover></button>
<button class="volume-button--small volume" @click="muteButtonPressed()" </div>
:class="{'active': this.cfg.audio.volume == 0}" <div class="app-chrome-item generic">
:title="cfg.audio.muted ? $root.getLz('term.unmute') : $root.getLz('term.mute')" v-b-tooltip.hover></button> <button class="playback-button--small queue" :title="$root.getLz('term.queue')" v-b-tooltip.hover
<input type="range" @wheel="volumeWheel" :step="cfg.audio.volumeStep" min="0" :max="cfg.audio.maxVolume" :class="{'active': drawer.panel == 'queue'}" @click="invokeDrawer('queue')"></button>
v-model="mk.volume" v-if="typeof mk.volume != 'undefined'" @change="checkMuteChange()" v-b-tooltip.hover </div>
:title="formatVolumeTooltip()" /> <div class="app-chrome-item generic">
</div> <template v-if="lyrics && lyrics != [] && lyrics.length > 0">
<div class="app-chrome-item generic"> <button class="playback-button--small lyrics" :title="$root.getLz('term.lyrics')" v-b-tooltip.hover
<button class="playback-button--small cast" :title="$root.getLz('term.cast')" :class="{'active': drawer.panel == 'lyrics'}" @click="invokeDrawer('lyrics')"></button>
@click="modals.castMenu = true" </template>
v-b-tooltip.hover></button> <template v-else>
</div> <button class="playback-button--small lyrics"
<div class="app-chrome-item generic"> :style="{'opacity': 0.3, 'pointer-events': 'none'}"></button>
<button class="playback-button--small queue" :title="$root.getLz('term.queue')" v-b-tooltip.hover </template>
:class="{'active': drawer.panel == 'queue'}" @click="invokeDrawer('queue')"></button> </div>
</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>
<template v-else> <template v-else>
<button class="playback-button--small lyrics" :style="{'opacity': 0.3, 'pointer-events': 'none'}" ></button> <div class="app-chrome-item search">
</template> <div class="search-input-container">
</div> <div class="search-input--icon"></div>
</template> <input type="search" spellcheck="false" @click="$root.appRoute('search');"
<template v-else> @focus="search.showHints = true"
<div class="app-chrome-item search"> @blur="setTimeout(()=>{search.showHints = false}, 300)"
<div class="search-input-container"> v-on:keyup.enter="searchQuery();search.showHints = false" @change="$root.appRoute('search');"
<div class="search-input--icon"></div> @input="getSearchHints()"
<input type="search" spellcheck="false" @click="$root.appRoute('search');" @focus="search.showHints = true" :placeholder="$root.getLz('term.search') + '...'" v-model="search.term" ref="searchInput"
@blur="setTimeout(()=>{search.showHints = false}, 300)" class="search-input" />
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-container" v-if="search.showHints && search.hints.length != 0">
<div class="search-hints"> <div class="search-hints">
<button class="search-hint text-overflow-elipsis" v-for="hint in search.hints" <button class="search-hint text-overflow-elipsis" v-for="hint in search.hints"
@click="search.term = hint;search.showHints = false;searchQuery(hint)"> @click="search.term = hint;search.showHints = false;searchQuery(hint)">
{{ hint }} {{ hint }}
</button> </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> </div>
</div> <div class="app-chrome-item full-height" v-else-if="platform != 'darwin' && !chrome.nativeControls">
</template> <button class="app-mainmenu" @blur="mainMenuVisibility(false)" @click="mainMenuVisibility(true)"
<div class="app-chrome-item full-height" id="window-controls-container" @contextmenu="mainMenuVisibility(true)" :class="{active: chrome.menuOpened}"></button>
v-if="chrome.windowControlPosition == 'right' && !chrome.nativeControls"> </div>
<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 class="app-chrome-item full-height" v-else-if="platform != 'darwin' && !chrome.nativeControls"> </div>
<button class="app-mainmenu" @blur="mainMenuVisibility(false)" @click="mainMenuVisibility(true)"
@contextmenu="mainMenuVisibility(true)" :class="{active: chrome.menuOpened}"></button>
</div>
</div>
</div>

View file

@ -50,10 +50,10 @@
<div id="apple-music-video-player-controls"> <div id="apple-music-video-player-controls">
<div id="player-exit" title="Close" @click="exitMV();fullscreen(false);"> <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" <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 <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" 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> </svg>
</div> </div>
<div id="captions" v-if="lyricon">{{((lyricon) ? ((lyrics.length > 0 && lyrics[currentLyricsLine] && <div id="captions" v-if="lyricon">{{((lyricon) ? ((lyrics.length > 0 && lyrics[currentLyricsLine] &&
@ -67,7 +67,7 @@
</div> </div>
<div class="playback-info music-player-info"> <div class="playback-info music-player-info">
<div class="song-artist-album-content" <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"> <div class="song-artist" style="display: inline-block">
{{ mk.nowPlayingItem?.attributes?.artistName ?? '' }} {{ mk.nowPlayingItem?.attributes?.artistName ?? '' }}
</div> </div>
@ -85,7 +85,7 @@
@mouseup="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);" @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);" @touchend="mk.seekToTime($event.target.value);setTimeout(()=>{playerLCD.desiredDuration = 0;playerLCD.userInteraction = false}, 1000);"
:max="mk.currentPlaybackDuration" :value="getSongProgress()"> :max="mk.currentPlaybackDuration" :value="getSongProgress()">
<p style="width: auto">{{ convertTime(mk.currentPlaybackDuration) }} <p style="width: auto">{{ convertTime(mk.currentPlaybackDuration) }}
</div> </div>
<div class="app-chrome-item display--large"> <div class="app-chrome-item display--large">
<div class="app-chrome-item volume display--large"> <div class="app-chrome-item volume display--large">
@ -93,7 +93,8 @@
:class="{'active': this.cfg.audio.volume == 0}" :class="{'active': this.cfg.audio.volume == 0}"
:title="cfg.audio.muted ? $root.getLz('term.unmute') : $root.getLz('term.mute')" :title="cfg.audio.muted ? $root.getLz('term.unmute') : $root.getLz('term.mute')"
v-b-tooltip.hover></button> 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-model="mk.volume" v-if="typeof mk.volume != 'undefined'" @change="checkMuteChange()"
v-b-tooltip.hover :title="formatVolumeTooltip()"> v-b-tooltip.hover :title="formatVolumeTooltip()">
</div> </div>
@ -102,30 +103,30 @@
<button class="playback-button play" @click="mk.play()" v-else <button class="playback-button play" @click="mk.play()" v-else
:title="$root.getLz('term.play')" v-b-tooltip.hover></button> :title="$root.getLz('term.play')" v-b-tooltip.hover></button>
<div class="app-chrome-item generic"> <div class="app-chrome-item generic">
<template v-if="lyrics && lyrics != [] && lyrics.length > 0"> <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')" :title="$root.getLz('term.lyrics')"
v-b-tooltip.hover v-b-tooltip.hover
:class="{'active': drawer.panel == 'lyrics'}" :class="{'active': drawer.panel == 'lyrics'}"
@click="invokeDrawer('lyrics')"></button> @click="invokeDrawer('lyrics')"></button>
</template> </template>
<template v-else> <template v-else>
<button class="playback-button--small lyrics" <button class="playback-button--small lyrics"
:style="{'opacity': 0.3, 'pointer-events': 'none'}"></button> :style="{'opacity': 0.3, 'pointer-events': 'none'}"></button>
</template> </template>
</div> </div>
<div id="player-pip" <div id="player-pip"
@click="pip()" @click="pip()"
title="Picture-in-Picture" title="Picture-in-Picture"
v-b-tooltip.hover> v-b-tooltip.hover>
<%- include("../svg/pip.svg") %> <%- include("../svg/pip.svg") %>
</div> </div>
<div id="player-fullscreen" <div id="player-fullscreen"
@click="fullscreen(!fullscreenState)" @click="fullscreen(!fullscreenState)"
title="Fullscreen" title="Fullscreen"
v-b-tooltip.hover> v-b-tooltip.hover>
<%- include("../svg/fullscreen.svg") %> <%- include("../svg/fullscreen.svg") %>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,88 +1,90 @@
<script type="text/x-template" id="add-to-playlist"> <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-fullscreen addtoplaylist-panel" @click.self="app.resetState()"
<div class="modal-window"> @contextmenu.self="app.resetState()">
<div class="modal-header"> <div class="modal-window">
<div class="modal-title">{{app.getLz('action.addToPlaylist')}}</div> <div class="modal-header">
<button class="close-btn" @click="app.resetState()" :aria-label="app.getLz('action.close')"></button> <div class="modal-title">{{app.getLz('action.addToPlaylist')}}</div>
</div> <button class="close-btn" @click="app.resetState()" :aria-label="app.getLz('action.close')"></button>
<div class="modal-content"> </div>
<button class="playlist-item" <div class="modal-content">
@click="app.addSelectedToNewPlaylist()" style="width:100%;"> <button class="playlist-item"
<div class="icon"><%- include("../svg/plus.svg") %></div> @click="app.addSelectedToNewPlaylist()" style="width:100%;">
<div class="name">{{app.getLz('action.createPlaylist')}}</div> <div class="icon"><%- include("../svg/plus.svg") %></div>
</button> <div class="name">{{app.getLz('action.createPlaylist')}}</div>
<sidebar-playlist :playlist-select="playlistSelect" :relate-media-items="relateItems" v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')" :item="item"> </button>
</sidebar-playlist> <sidebar-playlist :playlist-select="playlistSelect" :relate-media-items="relateItems"
</div> v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')" :item="item">
<div class="modal-search"> </sidebar-playlist>
<div class="search-input-container" style="width:100%;margin: 16px 0;"> </div>
<div class="search-input--icon"></div> <div class="modal-search">
<input type="search" <div class="search-input-container" style="width:100%;margin: 16px 0;">
ref="searchInput" <div class="search-input--icon"></div>
style="width:100%;" <input type="search"
spellcheck="false" ref="searchInput"
:placeholder="app.getLz('term.search') + '...'" style="width:100%;"
v-model="searchQuery" spellcheck="false"
@input="search()" :placeholder="app.getLz('term.search') + '...'"
class="search-input"> v-model="searchQuery"
</div> @input="search()"
class="search-input">
</div> </div>
</div> </div>
</div> </div>
</div>
</script> </script>
<script> <script>
Vue.component('add-to-playlist', { Vue.component('add-to-playlist', {
template: '#add-to-playlist', template: '#add-to-playlist',
data: function () { data: function() {
return { return {
playlistSorted: [], playlistSorted: [],
searchQuery: "", searchQuery: "",
focused: "", focused: "",
app: this.$root, app: this.$root,
relateItems: [] relateItems: []
} }
}, },
props: { props: {
playlists: { playlists: {
type: Array, type: Array,
required: true required: true
} }
}, },
mounted() { mounted() {
this.relateItems = [this.$root.selectedMediaItems[0].id] this.relateItems = [this.$root.selectedMediaItems[0].id]
this.search() this.search()
this.$refs.searchInput.focus() this.$refs.searchInput.focus()
this.$refs.searchInput.addEventListener('keydown', (e) => { this.$refs.searchInput.addEventListener('keydown', (e) => {
if (e.keyCode == 13) { if (e.keyCode == 13) {
if (this.focused != "") { if (this.focused != "") {
this.addToPlaylist(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
}
}
},
} }
}); })
</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>

View file

@ -1,42 +1,45 @@
<script type="text/x-template" id="airplay-modal"> <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="spatialproperties-panel castmenu modal-fullscreen airplay-modal" @click.self="close()"
<div class="modal-window airplay-modal"> @contextmenu.self="close()">
<div class="modal-header"> <div class="modal-window airplay-modal">
<div class="modal-title">{{'Enter password'}}</div> <div class="modal-header">
<button class="close-btn" @click="close()" :aria-label="this.$root.getLz('action.close')"></button> <div class="modal-title">{{'Enter password'}}</div>
</div> <button class="close-btn" @click="close()" :aria-label="this.$root.getLz('action.close')"></button>
<div class="modal-content" style="overflow-y: overlay; padding: 3%"> </div>
<input type="text" v-model="passcode"/> <div class="modal-content" style="overflow-y: overlay; padding: 3%">
</div> <input type="text" v-model="passcode" />
<div class="md-footer"> </div>
<div class="row"> <div class="md-footer">
<div class="col" > <div class="row">
<button style="width:100%" @click="enterPassword()" class="md-btn md-btn-block md-btn-primary">{{'OK'}}</button> <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> </div>
</div>
</div>
</script> </script>
<script> <script>
Vue.component('airplay-modal', { Vue.component('airplay-modal', {
template: '#airplay-modal', template: '#airplay-modal',
data: function () { data: function() {
return { return {
passcode: '', passcode: '',
} }
}, },
mounted() { mounted() {
}, },
methods: { methods: {
close() { close() {
this.$root.modals.airplayPW = false this.$root.modals.airplayPW = false
}, },
enterPassword() { enterPassword() {
console.log('Entered passCode: ', this.passcode) console.log('Entered passCode: ', this.passcode)
ipcRenderer.send("setAirPlayPasscode",this.passcode) ipcRenderer.send("setAirPlayPasscode", this.passcode)
this.close() this.close()
} }
} }
}); });
</script> </script>

View file

@ -5,93 +5,98 @@
</script> </script>
<script> <script>
Vue.component('animatedartwork-view', { Vue.component('animatedartwork-view', {
template: '#animatedartwork-view', template: '#animatedartwork-view',
data: function () { data: function() {
return { return {
app: this.$root, app: this.$root,
hls: null, hls: null,
} }
}, },
props: { props: {
video: { video: {
type: String, type: String,
required: true required: true
}, },
priority: { priority: {
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
async mounted() { async mounted() {
if (!this.priority && app.cfg.visual.animated_artwork == "limited") { if (!this.priority && app.cfg.visual.animated_artwork == "limited") {
return return
} else if (app.cfg.visual.animated_artwork == "disabled") { } else if (app.cfg.visual.animated_artwork == "disabled") {
return return
} }
if (this.video) { if (this.video) {
this.$nextTick(function () { this.$nextTick(function() {
var config = { var config = {
backBufferLength: 0, backBufferLength: 0,
enableWebVTT: false, enableWebVTT: false,
enableCEA708Captions: false, enableCEA708Captions: false,
emeEnabled: false, emeEnabled: false,
abrEwmaDefaultEstimate: 10000, abrEwmaDefaultEstimate: 10000,
testBandwidth: false, testBandwidth: false,
}; };
if (this.hls) { if (this.hls) {
this.hls.detachMedia(); this.hls.detachMedia();
} else { } else {
this.hls = new CiderHls(config); this.hls = new CiderHls(config);
} }
// bind them together // bind them together
if (this.$refs.video) { if (this.$refs.video) {
let d = "WIDEVINE_SOFTWARE" let d = "WIDEVINE_SOFTWARE"
let h = { let h = {
initDataTypes: ["cenc", "keyids"], initDataTypes: ["cenc", "keyids"],
distinctiveIdentifier: "optional", distinctiveIdentifier: "optional",
persistentState: "required" 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)
}
})
} }
}, let p = {
async beforeDestroy() { platformInfo: { requiresCDMAttachOnStart: !0, maxSecurityLevel: d, keySystemConfig: h },
if (this.hls) { appData: { serviceName: "Apple Music" }
await this.hls.destroy();
this.hls = null
} }
}
}); this.hls.attachMedia(this.$refs.video);
</script> 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>

View file

@ -15,14 +15,14 @@
<transition <transition
<% if(env.appRoutes[i].onEnter) { <% if(env.appRoutes[i].onEnter) {
%> %>
v-on:enter="<%- env.appRoutes[i].onEnter %>" v-on:enter="<%- env.appRoutes[i].onEnter; %>"
<% <%
} }
%> %>
:name="$root.chrome.desiredPageTransition"> :name="$root.chrome.desiredPageTransition">
<template <template
v-if="<%- env.appRoutes[i].condition %>"> v-if="<%- env.appRoutes[i].condition; %>">
<%- env.appRoutes[i].component %> <%- env.appRoutes[i].component; %>
</template> </template>
</transition> </transition>
<% } %> <% } %>
@ -38,14 +38,13 @@
</script> </script>
<script> <script>
Vue.component('app-content-area', { Vue.component('app-content-area', {
template: '#app-content-area', template: '#app-content-area',
data: function () { data: function() {
return { return {
scrollPos: 0 scrollPos: 0
} }
}, },
methods: { methods: {}
} });
}); </script>
</script>

Some files were not shown because too many files have changed in this diff Show more