Merge branch 'ciderapp:main' into update5

This commit is contained in:
椎名アヤネ 2022-08-04 15:54:51 +08:00 committed by GitHub
commit c50a3a3780
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
224 changed files with 65117 additions and 55922 deletions

View file

@ -27,6 +27,9 @@ jobs:
name: Restore Yarn Package Cache name: Restore Yarn Package Cache
keys: keys:
- yarn-packages-{{ checksum "cider.lock" }} - yarn-packages-{{ checksum "cider.lock" }}
- run:
name: Clear node_airtunes2 cache
command: sudo rm -rf ~/.cache/yarn/v6/.tmp/cf5bc2de2629636ca224995234b8eaa1 || true
- run: - run:
name: Install Node Dependencies name: Install Node Dependencies
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
@ -88,12 +91,6 @@ jobs:
sudo dpkg --add-architecture i386 sudo dpkg --add-architecture i386
sudo apt-get update -y sudo apt-get update -y
sudo apt-get install -y wine32 sudo apt-get install -y wine32
- run:
name: Reinstall proper rust node module
command: |
cd ./node_modules/cider_utils
yarn run prebuild-downloads --platform=win32 --verbose
cd ../..
- run: - run:
name: Generate Builds (Windows) name: Generate Builds (Windows)
command: yarn electron-builder -w --x64 -p never command: yarn electron-builder -w --x64 -p never
@ -119,12 +116,6 @@ jobs:
sudo dpkg --add-architecture i386 sudo dpkg --add-architecture i386
sudo apt-get update -y sudo apt-get update -y
sudo apt-get install -y wine32 sudo apt-get install -y wine32
- run:
name: Reinstall proper rust node module
command: |
cd ./node_modules/cider_utils
yarn run prebuild-downloads --platform=win32 --verbose
cd ../..
- run: - run:
name: Generate Builds (Winget) name: Generate Builds (Winget)
command: yarn electron-builder --win -c winget.json -p never command: yarn electron-builder --win -c winget.json -p never

12
.editorconfig Normal file
View file

@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false

View file

@ -1,31 +1,19 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "macOS build" name: "macOS build"
on: on:
push: push:
branches: [ main ] branches: [ main, stable ]
paths-ignore: paths-ignore:
- 'README.md' - 'README.md'
- 'SECURITY.md' - 'SECURITY.md'
- '.gitmodules' - '.gitmodules'
- '.gitignore' - '.gitignore'
- 'LICENSE' - 'LICENSE'
schedule: - 'cider.lock'
- cron: '44 20 * * 1'
jobs: jobs:
analyze: build-macos:
name: macOS build name: build-macos
runs-on: macos-11 runs-on: macos-11
permissions: permissions:
actions: read actions: read
@ -36,115 +24,102 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
language: [ 'javascript' ] language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps: steps:
- uses: maxim-lobanov/setup-xcode@v1 - uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: '12.4' xcode-version: '12.4'
- name: Checkout repository - uses: actions/checkout@v3
uses: actions/checkout@v2 with:
with: fetch-depth: 0
submodules: true
# Initializes the CodeQL tools for scanning. - name: Change Version
# - name: Initialize CodeQL run: sudo chmod +x resources/version.sh && ./resources/version.sh || true
# uses: github/codeql-action/init@v1
# with:
# languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Command-line programs to run using the OS shell. - name: Sign in to EVS
# 📚 https://git.io/JvXDl run: |
python3 -m pip install --upgrade castlabs-evs
python3 -m castlabs_evs.account refresh -A ${{ secrets.EVS_ACCOUNT_NAME }} -P ${{ secrets.EVS_PASSWD }}
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - name: Setup Environment
# and modify them (or add more) to build your code if your project run: brew install automake #libtool autoconf
# uses a compiled language
# - name : env - name: Install and Configure Node Modules
# run: | run: |
# export EVS_ACCOUNT_NAME=${{ secrets.EVS_ACCOUNT_NAME}} && export EVS_PASSWD=${{ secrets.EVS_PASSWD }} yarn install
# export CSC_LINK=${{ secrets.CSC_LINK }} && export CSC_KEY_PASSWORD=${{ secrets.CSC_KEY_PASSWORD }} cp resources/verror-types node_modules/@types/verror/index.d.ts
# export APPLEID=${{ secrets.APPLEID }} && export APPLEIDPASS=${{ secrets.APPLEIDPASS }} cp resources/macPackager.js node_modules/app-builder-lib/out/macPackager.js
rm -r node_modules/pouchdb-node/node_modules/leveldown
rm -r node_modules/pouchdb-adapter-leveldb/node_modules/leveldown
- name: Sign in to EVS - name: Build the DMG
run: | env:
python3 -m pip install --upgrade castlabs-evs CSC_LINK: ${{ secrets.CSC_LINK }}
python3 -m castlabs_evs.account refresh -A ${{ secrets.EVS_ACCOUNT_NAME }} -P ${{ secrets.EVS_PASSWD }} CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLEID: ${{ secrets.APPLEID }}
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
APPLE_ID: ${{ secrets.APPLEID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLEIDPASS }}
PSC_NAME: ${{ secrets.PSC_NAME }}
DEVELOPER_DIR: /Applications/Xcode_12.4.app/Contents/Developer
run: yarn dist:universalNotWorking -p never
- name: Add license to DMG
run: npx dmg-license resources/license.json dist/*.dmg
- name: Upload DMG
uses: svenstaro/upload-release-action@v2
with:
repo_name: ciderapp/cider-releases
repo_token: ${{ secrets.RELEASE_TOKEN }}
file: dist/Cider-${{ env.APP_VERSION }}-universal.dmg
tag: v${{ env.APP_VERSION }}
- name: Upload macOS Latest
uses: svenstaro/upload-release-action@v2
with:
repo_name: ciderapp/cider-releases
repo_token: ${{ secrets.RELEASE_TOKEN }}
file: dist/latest-mac.yml
tag: v${{ env.APP_VERSION }}
- name: Import
uses: apple-actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.CSC_LINK }}
p12-password: ${{ secrets.CSC_KEY_PASSWORD }}
- name: Build the PKG
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLEID: ${{ secrets.APPLEID }}
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
run: |
pkgbuild --component dist/mac-universal/Cider.app --install-location /Applications dist/Cider-${{ env.APP_VERSION }}-universal.pkg --sign ${{ secrets.PSC_NAME }}
xcrun altool --notarize-app --primary-bundle-id com.ciderapp.cider -f dist/Cider*.pkg --username ${{ secrets.APPLEID }} --password ${{ secrets.APPLEIDPASS }}
sleep 5m
xcrun stapler staple dist/Cider*.pkg || true
- name: Upload PKG
uses: svenstaro/upload-release-action@v2
with:
repo_name: ciderapp/cider-releases
repo_token: ${{ secrets.RELEASE_TOKEN }}
file: dist/Cider-${{ env.APP_VERSION }}-universal.pkg
tag: v${{ env.APP_VERSION }}
- name: Upload a Build Artifact
uses: actions/upload-artifact@v2.2.3
with:
name: Cider-macOS-${{ env.APP_VERSION }}
path: |
dist/*.dmg
dist/*.pkg
dist/latest-mac.yml
- name : Build
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLEID: ${{ secrets.APPLEID }}
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
APPLE_ID: ${{ secrets.APPLEID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLEIDPASS }}
PSC_NAME: ${{ secrets.PSC_NAME }}
DEVELOPER_DIR: /Applications/Xcode_12.4.app/Contents/Developer
run: |
rm cider-yarn.lock || true
xcodebuild -version
brew install autoconf automake libtool
yarn install
cp resources/verror-types node_modules/@types/verror/index.d.ts
cp resources/macPackager.js node_modules/app-builder-lib/out/macPackager.js
rm -r node_modules/pouchdb-node/node_modules/leveldown
rm -r node_modules/pouchdb-adapter-leveldb/node_modules/leveldown
rm -r /node_modules/leveldown/node_modules/node-gyp-build || true
yarn dist:universalNotWorking -p never
# - name: Perform CodeQL Analysis
# uses: github/codeql-action/analyze@v1
- name: Add license to dmg
run: |
npx dmg-license resources/license.json dist/*.dmg
- name: Import
uses: apple-actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.CSC_LINK }}
p12-password: ${{ secrets.CSC_KEY_PASSWORD }}
- name: Create PKG manually
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLEID: ${{ secrets.APPLEID }}
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
run: |
pkgbuild --component dist/mac-universal/Cider.app --install-location /Applications dist/Cider.pkg --sign ${{ secrets.PSC_NAME }}
xcrun altool --notarize-app --primary-bundle-id com.ciderapp.cider -f dist/Cider.pkg --username ${{ secrets.APPLEID }} --password ${{ secrets.APPLEIDPASS }}
sleep 5m
xcrun stapler staple dist/Cider.pkg || true
mv dist/*.dmg dist/Cider.dmg
- name: Upload a Build Artifact
uses: actions/upload-artifact@v2.2.3
with:
# Artifact name
name: macOS
# A file, directory or wildcard pattern that describes what to upload
path: |
dist/*.dmg
dist/*.pkg
# The desired behavior if no files are found using the provided path.
- name: Release
uses: softprops/action-gh-release@v1
with:
files: |
dist/Cider.dmg
dist/Cider.pkg
body: signed Develop MacOS Builds
name: macOS builds
tag_name: macos-beta
target_commitish: ${{ env.GITHUB_SHA }}
prerelease: true
generate_release_notes: true
fail_on_unmatched_files: false

4
.prettierignore Normal file
View file

@ -0,0 +1,4 @@
src/renderer/apple-hls*
build/*
src/renderer/lib/*
*.min.*

2
.prettierrc Normal file
View file

@ -0,0 +1,2 @@
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": {

View file

@ -35,52 +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",
"circle:script": "node resources/circle" "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",
"cider_utils": "git+https://github.com/ciderapp/cider_utils.git", "discord-auto-rpc": "^1.0.17",
"discord-auto-rpc": "^1.0.16",
"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"
}, },
@ -91,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": [
{ {
@ -181,8 +177,7 @@
"installLocation": "/Applications", "installLocation": "/Applications",
"background": { "background": {
"file": "./resources/bg.png", "file": "./resources/bg.png",
"alignment": "bottomleft", "alignment": "bottomleft"
"scaling": "tofit"
}, },
"allowAnywhere": true, "allowAnywhere": true,
"allowCurrentUserHome": true, "allowCurrentUserHome": true,

View file

@ -1,33 +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')) 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') 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')) 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') 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') // console.log('Castlabs-evs update start')
// execSync('python3 -m pip install --upgrade castlabs-evs') // execSync('python3 -m pip install --upgrade castlabs-evs')
// console.log('Castlabs-evs update complete') // console.log('Castlabs-evs update complete')
// xcode 13 // xcode 13
if (fs.existsSync('dist/mac-universal--x64') && fs.existsSync('dist/mac-universal--arm64') && fs.existsSync('dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib/keyedobjects-101300.nib')) if (
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'}) fs.existsSync("dist/mac-universal--x64") &&
fs.existsSync("dist/mac-universal--arm64") &&
fs.existsSync("dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib/keyedobjects-101300.nib")
)
execSync(
"cp 'dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib/keyedobjects-101300.nib' 'dist/mac-universal--arm64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib/keyedobjects-101300.nib'",
{ stdio: "inherit" }
);
// console.log('VMP signing start') // console.log('VMP signing start')
// if (fs.existsSync('dist/mac-universal')) // if (fs.existsSync('dist/mac-universal'))
// execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac-universal',{stdio: 'inherit'}) // execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac-universal',{stdio: 'inherit'})
// if (fs.existsSync('dist/mac')) // if (fs.existsSync('dist/mac'))
// execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac',{stdio: 'inherit'}) // execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac',{stdio: 'inherit'})
// if (fs.existsSync('dist/mac-arm64')) // if (fs.existsSync('dist/mac-arm64'))
// execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac-arm64 -z',{stdio: 'inherit'}) // execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac-arm64 -z',{stdio: 'inherit'})
if (fs.existsSync('dist/mac-x64') || fs.existsSync('dist/mac-universal--x64') ) // console.log('VMP signing complete')
execSync('cd ./node_modules/cider_utils; yarn run prebuild-downloads --platform=darwin --arch=arm64 --verbose; cd ../..',{stdio: 'inherit'}) };
// console.log('VMP signing complete')
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -1,44 +0,0 @@
if (!process.env['CIRCLECI']) {
console.log(`[CIRCLECI SCRIPT] CircleCI not found... Aborting script`)
return
}
const { readFileSync, writeFile } = require('fs')
const pkg = JSON.parse(readFileSync('package.json').toString());
let channel = process.env['CIRCLE_BRANCH'];
channel = channel.split('/').join('-')
// https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables
const version = pkg.version.split('.');
const patch = version[2].split('-');
if (process.env['CIRCLE_BRANCH'] === 'release') {
pkg.version = `${version[0]}.${version[1]}.${patch[0]}`
} else if (process.env['CIRCLE_BRANCH'] === 'main') {
pkg.version = `${version[0]}.${version[1]}.${patch[0]}.beta.${patch[1]}`
} else {
pkg.version = `${version[0]}.${version[1]}.${patch[0]}-${channel}.${process.env['CIRCLE_BUILD_NUM']}`
}
// package.build.channel = channel
pkg.publish = {
"provider": "github",
"repo": "cider-releases",
"owner": "ciderapp",
"vPrefixedTagName": true,
"tag": `v${pkg.version}`,
"channel": channel,
"releaseType": "release"
}
const { exec } = require('child_process')
exec(`echo $APP_VERSION`, { env: { 'APP_VERSION': pkg.version } }, function (error, stdout, stderr) {
console.log(stdout, stderr, error);
});
writeFile('package.json', JSON.stringify(pkg), err => {
// error checking
if (err) throw err;
console.log(`VERSION CHANGED TO ${pkg.version}`, pkg);
});

View file

@ -1,4 +0,0 @@
{
"key": "f9986d12aab5a0fe66193c559435ede3",
"secret": "acba3c29bd5973efa38cc2f0b63cc625"
}

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,20 +1,35 @@
#!/bin/bash #!/bin/bash
LATEST_SHA=$(curl -s https://api.github.com/repos/ciderapp/Cider/branches/stable | grep '"sha"' | head -1 | cut -d '"' -f 4) STABLE_SHA=$(curl -s https://api.github.com/repos/ciderapp/Cider/branches/stable | grep '"sha"' | head -1 | cut -d '"' -f 4)
SHA_DATE=$(git show -s --format=%ci $LATEST_SHA) SHA_DATE=$(git show -s --format=%ci $STABLE_SHA)
COMMITSINCESTABLE=$(git rev-list $LATEST_SHA..HEAD --count --since="$SHA_DATE") COMMITSINCESTABLE=$(git rev-list $STABLE_SHA..HEAD --count --since="$SHA_DATE")
CURRENT_VERSION=$(node -p -e "require('./package.json').version") CURRENT_VERSION=$(node -p -e "require('./package.json').version")
if [[ $CIRCLE_BRANCH == "main" && $COMMITSINCESTABLE -gt 0 ]]; then LATEST_VERSION=$(curl -s https://api.github.com/repos/ciderapp/cider-releases/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//' | xargs)
# Set the version number for commits on main branch
if [[ ($CIRCLE_BRANCH == "main" || $GITHUB_REF_NAME == "main") && $COMMITSINCESTABLE -gt 0 ]]; then
NEW_VERSION="${CURRENT_VERSION}-beta.${COMMITSINCESTABLE}" NEW_VERSION="${CURRENT_VERSION}-beta.${COMMITSINCESTABLE}"
# This is shit
if [[ $GITHUB_REF_NAME == "main" && $CURRENT_VERSION == $NEW_VERSION ]]; then
echo "No version could be made. Picking latest tag."
NEW_VERSION="${LATEST_VERSION}"
fi
# Update the version in package.json
if [[ $RUNNER_OS == "macOS" ]]; then
sed -i "" -e "s/$CURRENT_VERSION/$NEW_VERSION/" package.json
else
sed -i "0,/$CURRENT_VERSION/s//$NEW_VERSION/" package.json
fi
echo "Version updated to v${NEW_VERSION}"
else else
NEW_VERSION=${CURRENT_VERSION/0/$COMMITSINCESTABLE} echo "Not on main branch or no commits since stable. Skipping version update."
fi fi
if [[ $COMMITSINCESTABLE -gt 0 ]]; then # Add the version to the environment for CI usage
echo "Version: $NEW_VERSION" if [[ $GITHUB_REF_NAME != "" ]]; then
sed -i "0,/$CURRENT_VERSION/s//$NEW_VERSION/" package.json echo "APP_VERSION=$(node -p -e 'require("./package.json").version')" >>$GITHUB_ENV
else else
echo "Version unchanged, commits since stable is ${COMMITSINCESTABLE}" echo "export APP_VERSION=$(node -p -e 'require("./package.json").version')" >>$BASH_ENV
fi fi
echo "export APP_VERSION=$(node -p -e 'require("./package.json").version')" >>$BASH_ENV

View file

@ -1,61 +0,0 @@
import { _electron as electron } from "playwright";
import { test, expect } from "@playwright/test";
import { resolve } from "path";
import * as fs from "fs";
test("Launch electron app", async () => {
const paths = {
"mainBuild": resolve(__dirname, "../../build/"),
"main": resolve(__dirname, "../main"),
"root": resolve(__dirname, "../../"),
"cwd": __dirname,
"processcwd": process.cwd()
}
console.log(paths)
console.log(fs.readdirSync(paths.main))
const electronApp = await electron.launch({ args: ['build/index.js'], cwd: paths.root });
const appPath = await electronApp.evaluate(async ({ app }) => {
// This runs in the main Electron process, parameter here is always
// the result of the require('electron') in the main app script.
return app.getAppPath();
});
console.log(`cwd: ${appPath}`);
const windowState: { isVisible: boolean, isDevToolsOpened: boolean, isCrashed: boolean } = await electronApp.evaluate(async ({ BrowserWindow, app }) => {
const win = BrowserWindow.getAllWindows()[0];
console.log(win)
const getState = (win: Electron.BrowserWindow) => ({
isVisible: win.isVisible(),
isDevToolsOpened: win.webContents.isDevToolsOpened(),
isCrashed: win.webContents.isCrashed()
})
return new Promise((resolve) => {
if (win && win.isVisible()) {
resolve(getState(win))
} else if (win) {
win.once("ready-to-show", () => setTimeout(() => resolve(getState(win))));
} else {
app.once("browser-window-created", (_e: Event, window: Electron.BrowserWindow) => setTimeout(() => resolve(getState(window))));
}
})
})
console.log(windowState)
expect(windowState.isVisible).toBeTruthy();
expect(windowState.isDevToolsOpened).toBeFalsy();
expect(windowState.isCrashed).toBeFalsy();
await electronApp.close();
});

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

@ -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",

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",

View file

@ -341,10 +341,10 @@
"settings.option.general.resumebehavior.locally.description": "このコンピューターでの最終セッションを復元", "settings.option.general.resumebehavior.locally.description": "このコンピューターでの最終セッションを復元",
"settings.option.general.resumebehavior.history": "履歴", "settings.option.general.resumebehavior.history": "履歴",
"settings.option.general.resumebehavior.history.description": "Apple Musicの履歴から曲を復元", "settings.option.general.resumebehavior.history.description": "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" : "最後のセッションで開いていたタブを開きます", "settings.option.general.resumetabs.dynamic.description": "最後のセッションで開いていたタブを開きます",
"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": "未分類",
@ -398,7 +398,7 @@
"settings.warn.audio.enableAdvancedFunctionality.lowcores": "あなたのPCがこの処理に耐えられないかもしれません。", "settings.warn.audio.enableAdvancedFunctionality.lowcores": "あなたのPCがこの処理に耐えられないかもしれません。",
"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": "Designed by Cider Acoustic Technologies in California", "settings.option.audio.audioLab.subheader": "Designed by Cider Acoustic Technologies in California",
"settings.warn.audioLab.withoutAF": "Cider Audio Labを有効にするにはAudioContextを有効にする必要があります", "settings.warn.audioLab.withoutAF": "Cider Audio Labを有効にするにはAudioContextを有効にする必要があります",
"settings.warn.enableAdvancedFunctionality": "AudioContext (高度な機能) はこの機能を有効化する必要があります", "settings.warn.enableAdvancedFunctionality": "AudioContext (高度な機能) はこの機能を有効化する必要があります",
"settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Analog Warmth", "settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Analog Warmth",

View file

@ -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,7 +408,7 @@
"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": "Аналоговое звучание",
@ -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",

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,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
{ {
@ -338,89 +335,92 @@ export class AppEvents {
{type: 'separator'}, {type: 'separator'},
*/ */
{ {
visible: !visible, visible: !visible,
label: this.i18n['term.playpause'], label: this.i18n["term.playpause"],
click: () => { click: () => {
utils.getWindow().webContents.executeJavaScript('MusicKitInterop.playPause()') utils.getWindow().webContents.executeJavaScript("MusicKitInterop.playPause()");
} },
}, },
{ {
visible: !visible, visible: !visible,
label: this.i18n['term.next'], label: this.i18n["term.next"],
click: () => { click: () => {
utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.next()`) utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.next()`);
} },
}, },
{ {
visible: !visible, visible: !visible,
label: this.i18n['term.previous'], label: this.i18n["term.previous"],
click: () => { click: () => {
utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.previous()`) utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.previous()`);
} },
}, },
{type: 'separator', visible: !visible}, { type: "separator", visible: !visible },
{ {
label: (visible ? this.i18n['action.tray.minimize'] : `${this.i18n['action.tray.show']}`), label: visible ? this.i18n["action.tray.minimize"] : `${this.i18n["action.tray.show"]}`,
click: () => { click: () => {
if (utils.getWindow()) { if (utils.getWindow()) {
if (visible) { if (visible) {
utils.getWindow().hide() utils.getWindow().hide();
} else { } else {
utils.getWindow().show() utils.getWindow().show();
}
}
}
},
{
label: this.i18n['term.quit'],
click: () => {
app.quit()
}
} }
]) }
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,19 +13,19 @@ 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);
}; };

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,324 @@
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": [ staticOptimizer: {
"CommandOrControl", state: false,
"B" lock: false,
],
"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": { opportunisticCorrection_state: "OFF",
"discord_rpc": { atmosphereRealizer1: false,
"enabled": true, atmosphereRealizer1_value: "NATURAL_STANDARD",
"client": "Cider", atmosphereRealizer2: false,
"clear_on_pause": true, atmosphereRealizer2_value: "NATURAL_STANDARD",
"hide_buttons": false, spatial: false,
"hide_timestamp": false, spatialProfile: "BPLK",
"state_format": "by {artist}", vibrantBass: {
"details_format": "{title}", // 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],
"lastfm": { 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],
"enabled": false, 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],
"scrobble_after": 50, },
"filter_loop": false, },
"filter_types": {}, spatial: false,
"remove_featured": false, spatial_properties: {
"secrets": { presets: [],
"username": "", gain: 0.8,
"key": "" listener_position: [0, 0, 0],
} audio_position: [0, 0, 0],
room_dimensions: {
width: 32,
height: 12,
depth: 32,
},
room_materials: {
left: "metal",
right: "metal",
front: "brick-bare",
back: "brick-bare",
down: "acoustic-ceiling-tiles",
up: "acoustic-ceiling-tiles",
},
},
equalizer: {
preset: "default",
frequencies: [32, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
gain: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
Q: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
mix: 1,
vibrantBass: 0,
presets: [],
userGenerated: false,
},
},
visual: {
theme: "",
styles: [],
scrollbars: 0, // 0 = show on hover, 2 = always hide, 3 = always show
refresh_rate: 0,
window_background_style: "none", // "none", "artwork", "color"
animated_artwork: "limited", // 0 = always, 1 = limited, 2 = never
animated_artwork_qualityLevel: 1,
bg_artwork_rotation: false,
hw_acceleration: "default", // default, webgpu, disabled
showuserinfo: true,
transparent: false,
miniplayer_top_toggle: true,
directives: {
windowLayout: "default",
},
windowControlPosition: 0, // 0 default right
nativeTitleBar: false,
windowColor: "#000000",
customAccentColor: false,
accentColor: "#fc3c44",
purplePodcastPlaybackBar: false,
maxElementScale: -1, // -1 default, anything else is a custom scale
},
lyrics: {
enable_mxm: true,
mxm_karaoke: false,
mxm_language: "disabled",
enable_qq: false,
enable_yt: false,
},
advanced: {
AudioContext: true,
experiments: [],
playlistTrackMapping: true,
ffmpegLocation: "",
disableLogging: true,
},
connectUser: {
auth: null,
sync: {
themes: false,
plugins: false,
settings: false,
},
},
};
private migrations: any = {};
private schema: ElectronStore.Schema<any> = {
"connectivity.discord_rpc": {
type: "object",
},
};
}, 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 => {
@ -104,71 +103,69 @@ export class utils {
}) })
} */ } */
if (key) { if (key) {
return i18n[key] return i18n[key];
} else { } else {
return i18n return i18n;
}
} }
}
/** /**
* Gets a store value * Gets a store value
* @param key * @param key
* @returns store value * @returns store value
*/ */
static getStoreValue(key: string): any { static getStoreValue(key: string): any {
return Store.cfg.get(key) 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 {}
* Sets a store
* @returns store
*/
static getStore(): Object {
return Store.cfg.store
}
/** static loadJSFrontend(path: string): void {
* Get the store instance bw.win.webContents.executeJavaScript(fs.readFileSync(path, "utf8"));
* @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 {
}
static loadJSFrontend(path: string): void {
bw.win.webContents.executeJavaScript(fs.readFileSync(path, "utf8"));
}
} }

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'",

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);
}
});
function getLocation(headers: any) {
let location = null;
if (headers["LOCATION"] != null) {
location = headers["LOCATION"]
} else if (headers["Location"] != null) {
location = headers["Location"]
}
return location;
}
ssdpBrowser.search('urn:dial-multiscreen-org:device:dial:1');
// // actual upnp devices
// if (app.cfg.get("audio.enableDLNA")) {
// let ssdpBrowser2 = new Client();
// ssdpBrowser2.on('response', (headers, statusCode, rinfo) => {
// var location = getLocation(headers);
// if (location != null) {
// this.getServiceDescription(location, rinfo.address);
// }
// });
// ssdpBrowser2.search('urn:schemas-upnp-org:device:MediaRenderer:1');
// }
} catch (e) {
console.log('Search GC err', e);
}
}
private getServiceDescription(url: any, address: any) {
const request = require('request');
request.get(url, (error: any, response: any, body: any) => {
if (!error && response.statusCode === 200) {
this.parseServiceDescription(body, address, url);
}
});
}
private ondeviceup(host: any, name: any, location: any, type: any) {
if (this.castDevices.findIndex((item: any) => item.host === host && item.name === name && item.location === location && item.type === type) === -1) {
this.castDevices.push({
name: name,
host: host,
location: location,
type: type
});
if (this.devices.indexOf(host) === -1) {
this.devices.push(host);
}
if (name) {
this._win.webContents.executeJavaScript(`console.log('deviceFound','ip: ${host} name:${name}')`).catch((err: any) => console.error(err));
console.log("deviceFound", host, name);
}
} else {
this._win.webContents.executeJavaScript(`console.log('deviceFound (added)','ip: ${host} name:${name}')`).catch((err: any) => console.error(err));
console.log("deviceFound (added)", host, name);
}
}
private parseServiceDescription(body: any, address: any, url: any) {
const parseString = require('xml2js').parseString;
parseString(body, (err: any, result: any) => {
if (!err && result && result.root && result.root.device) {
const device = result.root.device[0];
console.log('device', device);
let devicetype = 'googlecast';
console.log()
if (device.deviceType && device.deviceType.toString() === 'urn:schemas-upnp-org:device:MediaRenderer:1') {
devicetype = 'upnp';
}
this.ondeviceup(address, device.friendlyName.toString(), url, devicetype);
}
});
}
private loadMedia(client: any, song: any, artist: any, album: any, albumart: any, cb?: any) {
// const u = 'http://' + this.getIp() + ':' + server.address().port + '/';
// const DefaultMediaReceiver : any = require('castv2-client').DefaultMediaReceiver;
client.launch(CiderReceiver, (err: any, player: any) => {
if (err) {
console.log(err);
return;
}
let media = {
// Here you can plug an URL to any mp4, webm, mp3 or jpg file with the proper contentType.
contentId: 'http://' + this.getIp() + ':'+ this.ciderPort +'/audio.wav',
contentType: 'audio/wav',
streamType: 'LIVE', // or LIVE
// Title and cover displayed while buffering
metadata: {
type: 0,
metadataType: 3,
title: song ?? "",
albumName: album ?? "",
artist: artist ?? "",
images: [
{url: albumart ?? ""}]
}
};
player.on('status', (status: any) => {
console.log('status broadcast playerState=%s', status);
});
console.log('app "%s" launched, loading media %s ...', player, media);
player.load(media, {
autoplay: true
}, (err: any, status: any) => {
console.log('media loaded playerState=%s', status);
});
client.getStatus((x: any, status: any) => {
if (status && status.volume) {
client.volume = status.volume.level;
client.muted = status.volume.muted;
client.stepInterval = status.volume.stepInterval;
}
})
// send websocket ip
player.sendIp("ws://" + this.getIp() + ":26369");
electron.ipcMain.on('stopGCast', (_event) => {
player.kill();
})
electron.app.on('before-quit', (_event) => {
player.kill();
})
});
}
private getIp(){
let ip: string = '';
let ip2: any = [];
let alias = 0;
const ifaces: any = os.networkInterfaces();
for (let dev in ifaces) {
ifaces[dev].forEach((details: any) => {
if (details.family === 'IPv4' && !details.internal) {
if (!/(loopback|vmware|internal|hamachi|vboxnet|virtualbox)/gi.test(dev + (alias ? ':' + alias : ''))) {
if (details.address.substring(0, 8) === '192.168.' ||
details.address.substring(0, 7) === '172.16.' ||
details.address.substring(0, 3) === '10.'
) {
if (!ip.startsWith('192.168.') ||
(ip2.startsWith('192.168.') && !ip.startsWith('192.168.')) &&
(ip2.startsWith('172.16.') && !ip.startsWith('192.168.') && !ip.startsWith('172.16.')) ||
(ip2.startsWith('10.') && !ip.startsWith('192.168.') && !ip.startsWith('172.16.') && !ip.startsWith('10.'))
){ip = details.address;}
++alias;
}
}
}
});
}
return ip;
}
private stream(device: any, song: any, artist: any, album: any, albumart: any) {
let castMode = 'googlecast';
let UPNPDesc = '';
castMode = device.type;
UPNPDesc = device.location;
let client;
if (castMode === 'googlecast') {
let client = new this.audioClient();
client.volume = 100;
client.stepInterval = 0.5;
client.muted = false;
client.connect(device.host, () => {
// console.log('connected, launching app ...', 'http://' + this.getIp() + ':' + server.address().port + '/');
if (!this.connectedHosts[device.host]) {
this.connectedHosts[device.host] = client;
this.activeConnections.push(client);
}
this.loadMedia(client, song, artist, album, albumart);
});
client.on('close', () => {
console.info("Client Closed");
for (let i = this.activeConnections.length - 1; i >= 0; i--) {
if (this.activeConnections[i] === client) {
this.activeConnections.splice(i, 1);
return;
}
}
});
client.on('error', (err: any) => {
console.log('Error: %s', err.message);
client.close();
delete this.connectedHosts[device.host];
});
} else {
// upnp devices
//try {
// client = new MediaRendererClient(UPNPDesc);
// const options = {
// autoplay: true,
// contentType: 'audio/x-wav',
// dlnaFeatures: 'DLNA.ORG_PN=-;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000',
// metadata: {
// title: 'Apple Music Electron',
// creator: 'Streaming ...',
// type: 'audio', // can be 'video', 'audio' or 'image'
// // url: 'http://' + getIp() + ':' + server.address().port + '/',
// // protocolInfo: 'DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000;
// }
// };
// client.load('http://' + getIp() + ':' + server.address().port + '/a.wav', options, function (err, _result) {
// if (err) throw err;
// console.log('playing ...');
// });
// } catch (e) {
// }
}
}
private async setupGCServer() {
return ''
}
/**
* Base Plugin Details (Eventually implemented into a GUI in settings)
*/
public name: string = 'Chromecast';
public description: string = 'LastFM plugin for Cider';
public version: string = '0.0.1';
public author: string = 'vapormusic / Cider Collective';
/**
* Runs on plugin load (Currently run on application start)
*/
constructor(utils: { getApp: () => any; getStore: () => any; }) {
this._app = utils.getApp();
this._store = utils.getStore()
}
/**
* Runs on app ready
*/
onReady(win: any): void {
this._win = win;
electron.ipcMain.on('getKnownCastDevices', (event) => {
event.returnValue = this.castDevices
});
electron.ipcMain.on('performGCCast', (event, device, song, artist, album, albumart) => {
// this.setupGCServer().then( () => {
this._win.webContents.setAudioMuted(true);
console.log(device);
this.stream(device, song, artist, album, albumart);
// })
});
electron.ipcMain.on('getChromeCastDevices', (_event, _data) => {
this.searchForGCDevices();
});
electron.ipcMain.on('stopGCast', (_event) => {
this._win.webContents.setAudioMuted(false);
this.activeConnections.forEach((client: any) => {
try{
client.stop();
} catch(e){}
})
this.activeConnections = [];
this.connectedHosts = {};
})
}
/**
* Runs on app stop
*/
onBeforeQuit(): void {
}
/**
* Runs on song change
* @param attributes Music Attributes
*/
onNowPlayingItemDidChange(attributes: any): void {
}
onRendererReady(): void {
this._win.webContents.executeJavaScript(
`ipcRenderer.sendSync('get-port')`
).then((result: any) => {
this.ciderPort = result;
});
}
onRendererReady(): void {
this._win.webContents.executeJavaScript(`ipcRenderer.sendSync('get-port')`).then((result: any) => {
this.ciderPort = result;
});
}
} }

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)
}
})
}
}
/**
* Scrobbles the track to lastfm
* @param attributes
* @private
*/
private scrobbleTrack(attributes: any): void {
if (!attributes?.lfmTrack || !attributes?.lfmAlbum) {
this.verifyTrack(attributes, (a: any) => {
this.scrobbleTrack(a)
})
return
}
if (!this._authenticated || !attributes || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] || (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._scrobbleCache.track === attributes.lfmTrack.name)) return;
// Scrobble
const scrobble = {
'artist': attributes.lfmTrack.artist.name,
'track': attributes.lfmTrack.name,
'album': attributes.lfmAlbum.name,
'albumArtist': attributes.lfmAlbum.artist,
'timestamp': new Date().getTime() / 1000,
'trackNumber': attributes.trackNumber,
'duration': attributes.durationInMillis / 1000,
}
// Easy Debugging
console.debug(`[${lastfm.name}:scrobble] Scrobbling ${scrobble.artist} - ${scrobble.track}`)
// Scrobble the track
this._lfm.track.scrobble(scrobble, (err: any, _res: any) => {
if (err) {
console.error(`[${lastfm.name}:scrobble] Scrobble failed: ${err.message}`);
} else {
console.debug(`[${lastfm.name}:scrobble] Track scrobbled: ${scrobble.artist} - ${scrobble.track}`);
this._scrobbleCache = scrobble
}
});
}
/**
* Updates the now playing track
* @param attributes
* @private
*/
private updateNowPlayingTrack(attributes: any): void {
if (!attributes?.lfmTrack || !attributes?.lfmAlbum) {
this.verifyTrack(attributes, (a: any) => {
this.updateNowPlayingTrack(a)
})
return
}
if (!this._authenticated || !attributes || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] || (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._nowPlayingCache.track === attributes.lfmTrack.name)) return;
const nowPlaying = {
'artist': attributes.lfmTrack.artist.name,
'track': attributes.lfmTrack.name,
'album': attributes.lfmAlbum.name,
'trackNumber': attributes.trackNumber,
'duration': attributes.durationInMillis / 1000,
'albumArtist': attributes.lfmAlbum.artist,
}
this._lfm.track.updateNowPlaying(nowPlaying, (err: any, res: any) => {
if (err) {
console.error(`[${lastfm.name}:updateNowPlaying] Now Playing Update failed: ${err.message}`);
} else {
console.debug(`[${lastfm.name}:updateNowPlaying] Now Playing Updated: ${nowPlaying.artist} - ${nowPlaying.track}`);
this._nowPlayingCache = nowPlaying
}
});
}
this._lfm.track.updateNowPlaying(nowPlaying, (err: any, res: any) => {
if (err) {
console.error(`[${lastfm.name}:updateNowPlaying] Now Playing Update failed: ${err.message}`);
} else {
console.debug(`[${lastfm.name}:updateNowPlaying] Now Playing Updated: ${nowPlaying.artist} - ${nowPlaying.track}`);
this._nowPlayingCache = nowPlaying;
}
});
}
} }

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,111 +1,118 @@
import fetch from "electron-fetch"; import fetch from "electron-fetch";
import {nativeImage, Notification} from "electron"; import { app, nativeImage, Notification } from "electron";
import NativeImage = Electron.NativeImage; import NativeImage = Electron.NativeImage;
import { createWriteStream } from "fs";
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="ToastText02"> <binding template="ToastImageAndText02">
<text id="1">${a?.name.replace(/&/g, '&amp;')}</text> <image id="1" src="${join(app.getPath("temp"), `${a.songId}-${a.artwork.url.split("/").pop()}`)}" name="Image" />
<text id="2">${a?.artistName.replace(/&/g, '&amp;')} ${a?.albumName.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>
</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>`,
}); });
// image implementation in windows toasts console.log(this._notification.toastXml);
//<image id="1" src="path to image" alt="img"/>
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 {
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 processNode = `
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 = `
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) => {
// if (service.addresses && service.fullname && (service.fullname.includes('_raop._tcp') || service.fullname.includes('_airplay._tcp'))) {
// // console.log(service.txt)
// this._win.webContents.executeJavaScript(`console.log(
// "${service.name} ${service.host}:${service.port} ${service.addresses}"
// )`);
// this.ondeviceup(service.name, service.host, service.port, service.addresses, service.txt);
// }
// });
electron.ipcMain.on("performAirplayPCM", (event, ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv) => {
if (ipv4 != this.ipairplay || ipport != this.portairplay) {
if (this.airtunes == null) {
this.airtunes = new this.u();
}
this.ipairplay = ipv4;
this.portairplay = ipport;
this.device = this.airtunes.add(ipv4, {
port: ipport,
volume: 50,
password: sepassword,
txt: txt,
airplay2: airplay2dv,
debug: true,
}); });
// 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,258 +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";
import { parseFile, recursiveFolderSearch } from 'cider_utils';
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 = {
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);
static async scanLibrary() { if (this.localSongs.length === 0 && numid % 10 === 0) {
ProviderDB.init() // send updated chunks only if there is no previous database
let folders = utils.getStoreValue("libraryPrefs.localPaths") this.eventEmitter.emit("newtracks", metadatalist);
if (folders == null || folders.length == null || folders.length == 0) folders = [] }
console.log('folders', folders)
let parseFileQueue: any[] = []; let mmQueue: any[] = []
for (var folder of folders) {
// Recursively search and add
let result = await recursiveFolderSearch(folder)
parseFileQueue = parseFileQueue.concat(result.parseFile)
mmQueue = mmQueue.concat(result.musicMetadata)
} }
if (parseFileQueue.length !== 0 || mmQueue.length !== 0) {console.log('Recursive Folder Search in Cider Utils worki')} } catch (e) {
let metadatalist = [] console.error("localfiles error:", e);
let metadatalistart = [] }
let numid = 0;
mmQueue = mmQueue.concat(parseFileQueue)
parseFileQueue = []
// Music Metadata fallback
for (var audio of mmQueue) {
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("error:", e)}
}
// Cider-Utils supported formats.
for (var audio of parseFileQueue) {
try {
const metadata = await 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.track_number ?? 0,
"discNumber": metadata.disc_number ?? 0,
"name": metadata.title == 0 ? audio.substring(audio.lastIndexOf('\\') + 1) : metadata.title,
"albumName": metadata.album,
"artistName": metadata.artist,
"copyright": metadata.copyright ?? "",
"assetUrl": "file:///" + audio,
"contentAdvisory": "",
"releaseDateTime": `${metadata.year ?? '2022'}-05-13T00:23:00Z`,
"durationInMillis": metadata.duration_in_ms ?? 0,
"bitrate": metadata.bitrate ?? 0,
"offers": [
{
"kind": "get",
"type": "STDQ"
}
],
"contentRating": "clean"
},
flavor: metadata.bitrate,
localFilesMetadata: {
lossless: metadata.lossless,
container: metadata.container,
bitDepth: metadata.bit_depth,
sampleRate: metadata.sample_rate ?? 0,
},
};
let art = {
id: "ciderlocal" + lochash,
_id: "ciderlocalart" + lochash,
url: metadata.artwork != undefined ? metadata.artwork : "",
}
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("error:", e)}
}
this.localSongs = metadatalist;
this.localSongsArts = metadatalistart;
return metadatalist;
} }
// 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 async getFiles(dir: any) { ProviderDB.db.get(hash).then(function (doc: any) {
const dirents = await readdir(dir, { withFileTypes: true }); return ProviderDB.db.remove(doc);
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 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);
}); });
} 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

Binary file not shown.

View file

@ -0,0 +1,963 @@
const CiderAudioRenderer = {
context: null,
source: null,
audioNodes: {
gainNode: null,
spatialNode: null,
audioBands: null,
vibrantbassNode: null,
llpw: null,
recorderNode: null,
intelliGainComp: null,
atmosphereRealizer2: null,
atmosphereRealizer1: null,
opportunisticCorrection: null
},
off: function () {
CiderAudioRenderer.hierarchical_unloading();
try {
CiderAudioRenderer.audioNodes = {
gainNode: null,
spatialNode: null,
audioBands: null,
vibrantbassNode: null,
llpw: null,
recorderNode: null,
intelliGainComp: null,
atmosphereRealizer2: null,
atmosphereRealizer1: null,
opportunisticCorrection: null
}
} catch (e) { }
},
init: function () {
CiderAudioRenderer.context = new OfflineAudioContext({
numberOfChannels: 2,
length: 96000 * 8,
sampleRate: 96000,
})
CiderAudioRenderer.hierarchical_loading();
},
optimizerProfile: [
{
"id": 'dirac32_96',
"file": './cideraudio/impulses/OptimizerDirac32_96.wav',
"bitDepth": 32,
"sampleRate": 96000,
"container": "WAV"
}
],
spatialProfiles: [
{
"id": "maikiwi",
"file": './cideraudio/impulses/CiderSpatial_Maikiwi.wav',
"name": "Maikiwi",
"description": "",
"gainComp": "1.044"
},
{
"id": "maikiwiplus",
"file": './cideraudio/impulses/CiderSpatial_MaikiwiPlus.wav',
"name": "Maikiwi+",
"description": "",
"gainComp": "1.044"
},
{
"id": "71_420maikiwi",
"file": './cideraudio/impulses/CiderSpatial_v71.420_Maikiwi.wav',
"name": "Soundstage",
"description": "",
"gainComp": "1.3963683610559376"
},
{
"id": "70_422maikiwi",
"file": './cideraudio/impulses/CiderSpatial_v70.422_Maikiwi.wav',
"name": "Separation",
"description": "",
"gainComp": "1.30767553892022"
},
{
"id": "standard",
"file": './cideraudio/impulses/CiderSpatial_Natural.wav',
"name": "Minimal",
"description": "",
"gainComp": "1.044"
},
{
"id": "standardplus",
"file": './cideraudio/impulses/CiderSpatial_Natural+.wav',
"name": "Minimal+",
"description": "",
"gainComp": "1.044"
},
{
"id": "diffused",
"file": './cideraudio/impulses/CiderSpatial_Diffuse.wav',
"name": "Diffused",
"description": "",
"gainComp": "1.044"
},
{
"id": "BPLK",
"file": './cideraudio/impulses/CiderSpatial_BPLK.wav',
"name": "BPLK",
"description": "",
"gainComp": "1.044"
},
{
"id": "HW2K",
"file": './cideraudio/impulses/CiderSpatial_HW2K.wav',
"name": "HW2K",
"description": "",
"gainComp": "1.044"
},
{
"id": "live",
"file": './cideraudio/impulses/CiderSpatial_LIVE_2.wav',
"name": "live",
"description": "",
"gainComp": "1.2647363474711515"
}
],
atmosphereRealizerProfiles: [
{
"id": "NATURAL_STANDARD",
"file": './cideraudio/impulses/AtmosphereRealizer_NaturalStandard.wav',
"name": "ほうじ茶チーズクリーマティー",
"description": "",
},
{
"id": "NATURAL_PLUS",
"file": './cideraudio/impulses/AtmosphereRealizer_Natural+.wav',
"name": "玄米茶タピオカミルクティー",
"description": "",
},
{
"id": "E68_1",
"file": './cideraudio/impulses/AtmosphereRealizer_E68_1.5.wav',
"name": "岩塩クリームチーズティー",
"description": "Light",
},
{
"id": "E68_2",
"file": './cideraudio/impulses/AtmosphereRealizer_E68_2.2.wav',
"name": "抹茶ミルクティー",
"description": "Dark",
},
{
"id": "BSCBM",
"file": './cideraudio/impulses/AtmosphereRealizer_BSCBM.wav',
"name": "BSCBM",
"description": "BSCBM",
},
{
"id": "CUDDLE",
"file": './cideraudio/impulses/AtmosphereRealizer_Cuddle.wav',
"name": "CUDDLE",
"description": "CUDDLE",
},
{
"id": "E168_1",
"file": './cideraudio/impulses/AtmosphereRealizer_E168_1.2.wav',
"name": "春毫ジャスミンマキアート",
"description": "Natural Air",
},
{
"id": "Z3600",
"file": './cideraudio/impulses/AtmosphereRealizer_Z3600.wav',
"name": "ロイヤルミルクティー",
"description": "3600",
},
{
"id": "Z8500A",
"file": './cideraudio/impulses/AtmosphereRealizer_Z8500_A.wav',
"name": "ムーンライトソフトケーキ",
"description": "8500",
},
{
"id": "Z8500B",
"file": './cideraudio/impulses/AtmosphereRealizer_Z8500_B.wav',
"name": "Clafoutis aux Cerises",
"description": "8500",
},
{
"id": "Z8500C",
"file": './cideraudio/impulses/AtmosphereRealizer_Z8500_C.wav',
"name": "宇治抹茶だいふく",
"description": "8500",
}
],
opportunisticCorrectionProfiles: [
{
"id": "CHU",
"file": './cideraudio/impulses/MoondropCHU_Cider.wav',
"name": "Moondrop CHU Specific",
"description": "",
}
],
spatial_ninf: function () {
CiderAudioRenderer.audioNodes.spatialNode = null;
CiderAudioRenderer.audioNodes.spatialNode = CiderAudioRenderer.context.createConvolver();
CiderAudioRenderer.audioNodes.spatialNode.normalize = false;
let spatialProfile = CiderAudioRenderer.spatialProfiles.find(function (profile) {
return profile.id === app.cfg.audio.maikiwiAudio.spatialProfile;
});
if (spatialProfile === undefined) {
spatialProfile = CiderAudioRenderer.spatialProfiles[0];
}
fetch(spatialProfile.file).then(async (impulseData) => {
let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudioRenderer.audioNodes.spatialNode.buffer = await CiderAudioRenderer.context.decodeAudioData(bufferedImpulse);
});
// Always destination
CiderAudioRenderer.audioNodes.spatialNode.connect(CiderAudioRenderer.context.destination)
},
spatialOff: function () {
CiderAudioRenderer.hierarchical_loading();
},
intelliGainComp_n0_0: function () {
let filters = []; const precisionHz = 12;
// Biquad calculation
if (CiderAudioRenderer.audioNodes.audioBands !== null) { filters = filters.concat(CiderAudioRenderer.audioNodes.audioBands) }
if (CiderAudioRenderer.audioNodes.vibrantbassNode !== null) { filters = filters.concat(CiderAudioRenderer.audioNodes.vibrantbassNode) }
if (CiderAudioRenderer.audioNodes.llpw !== null && CiderAudioRenderer.audioNodes.llpw.length > 2) { filters = filters.concat(CiderAudioRenderer.audioNodes.llpw); }
if (!filters || filters.length === 0) {
let filterlessGain = 1;
// Impulse Calculation
if (CiderAudioRenderer.audioNodes.llpw !== null && CiderAudioRenderer.audioNodes.llpw.length <= 2) { filterlessGain = filterlessGain * 1.109174815262401 }
if (app.cfg.audio.maikiwiAudio.atmosphereRealizer2 === true) { filterlessGain = filterlessGain * 1.096478196143185 }
if (app.cfg.audio.maikiwiAudio.atmosphereRealizer1 === true) { filterlessGain = filterlessGain * 1.096478196143185 }
if (app.cfg.audio.maikiwiAudio.spatial == true) {
let spatialProfile = CiderAudioRenderer.spatialProfiles.find(function (profile) {
return profile.id === app.cfg.audio.maikiwiAudio.spatialProfile;
});
if (spatialProfile === undefined) {
spatialProfile = CiderAudioRenderer.spatialProfiles[0];
}
filterlessGain = filterlessGain * spatialProfile.gainComp
}
filterlessGain = Math.pow(10, (-1 * (20 * Math.log10(filterlessGain))) / 20).toFixed(4);
filterlessGain > 1.0 ? CiderAudioRenderer.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(1.0, CiderAudioRenderer.context.currentTime + 0.3) : CiderAudioRenderer.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(filterlessGain, CiderAudioRenderer.context.currentTime + 0.3);
console.debug(`[Cider][Audio] IntelliGainComp: ${filterlessGain > 1.0 ? 0 : (20 * Math.log10(filterlessGain)).toFixed(2)} dB (${filterlessGain > 1.0 ? 1 : filterlessGain})`)
return;
}
filters.shift();
let steps = Math.ceil(96000 / precisionHz);
// Generate input array for getFrequencyResponse method
let frequencies = new Float32Array(steps);
for (let i = 0; i < steps; i++) {
frequencies[i] = (i + 1) * precisionHz;
}
// Here we will store combined amplitude response
let totalAmplitudeResp = new Float32Array(steps);
for (let i = 0; i < steps; i++) {
totalAmplitudeResp[i] = 1;
}
// Temporary container for every filter response
let amplitudeResp = new Float32Array(steps), phaseResp = new Float32Array(steps);
for (let i = filters.length - 1; i >= 0; i--) {
let filter = filters[i];
// Get filter response and convolve it with existing response
filter.getFrequencyResponse(frequencies, amplitudeResp, phaseResp);
for (let j = 0; j < steps; j++) {
totalAmplitudeResp[j] *= amplitudeResp[j];
}
}
// Find max gain
let maxGain = -120
for (let i = 0; i < steps; i++) {
let gain = totalAmplitudeResp[i];
if (gain > maxGain)
maxGain = gain;
}
// Impulse Calculation
if (CiderAudioRenderer.audioNodes.llpw !== null && CiderAudioRenderer.audioNodes.llpw.length <= 2) { maxGain = maxGain * 1.109174815262401 }
if (app.cfg.audio.maikiwiAudio.atmosphereRealizer2 === true) { maxGain = maxGain * 1.096478196143185 }
if (app.cfg.audio.maikiwiAudio.atmosphereRealizer1 === true) { maxGain = maxGain * 1.096478196143185 }
if (app.cfg.audio.maikiwiAudio.spatial == true) {
let spatialProfile = CiderAudioRenderer.spatialProfiles.find(function (profile) {
return profile.id === app.cfg.audio.maikiwiAudio.spatialProfile;
});
if (spatialProfile === undefined) {
spatialProfile = CiderAudioRenderer.spatialProfiles[0];
}
maxGain = maxGain * spatialProfile.gainComp
}
maxGain = Math.pow(10, (-1 * (20 * Math.log10(maxGain))) / 20).toFixed(4);
maxGain > 1.0 ? CiderAudioRenderer.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(1.0, CiderAudioRenderer.context.currentTime + 0.3) : CiderAudioRenderer.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(maxGain, CiderAudioRenderer.context.currentTime + 0.3);
console.debug(`[Cider][Audio] IntelliGainComp: ${maxGain > 1.0 ? 0 : (20 * Math.log10(maxGain)).toFixed(2)} dB (${maxGain > 1.0 ? 1 : maxGain})`);
},
atmosphereRealizer2_n6: function (status, destination) {
if (status === true) {
CiderAudioRenderer.audioNodes.atmosphereRealizer2 = CiderAudioRenderer.context.createConvolver();
CiderAudioRenderer.audioNodes.atmosphereRealizer2.normalize = false;
let atmosphereRealizerProfile = CiderAudioRenderer.atmosphereRealizerProfiles.find(function (profile) {
return profile.id === app.cfg.audio.maikiwiAudio.atmosphereRealizer2_value;
});
if (atmosphereRealizerProfile === undefined) {
atmosphereRealizerProfile = CiderAudioRenderer.atmosphereRealizerProfiles[0];
}
fetch(atmosphereRealizerProfile.file).then(async (impulseData) => {
let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudioRenderer.audioNodes.atmosphereRealizer2.buffer = await CiderAudioRenderer.context.decodeAudioData(bufferedImpulse);
});
switch (destination) {
case "spatial":
try { CiderAudioRenderer.audioNodes.atmosphereRealizer2.connect(CiderAudioRenderer.audioNodes.spatialNode); console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> Spatial");} catch (e) { }
break;
case "n6":
try {
CiderAudioRenderer.audioNodes.atmosphereRealizer2.connect(CiderAudioRenderer.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n5':
try {
CiderAudioRenderer.audioNodes.atmosphereRealizer2.connect(CiderAudioRenderer.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> atmosphereRealizer1");
} catch (e) { }
break;
case 'n4':
try {
CiderAudioRenderer.audioNodes.atmosphereRealizer2.connect(CiderAudioRenderer.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> vibrantbassNode");
} catch (e) { }
break;
case 'n3':
try {
CiderAudioRenderer.audioNodes.atmosphereRealizer2.connect(CiderAudioRenderer.audioNodes.audioBands[0]);
console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> audioBands");
} catch (e) { }
break;
case 'n2':
try {
CiderAudioRenderer.audioNodes.atmosphereRealizer2.connect(CiderAudioRenderer.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> opportunisticCorrection");
} catch (e) { }
break;
case 'n1':
try {
CiderAudioRenderer.audioNodes.atmosphereRealizer2.connect(CiderAudioRenderer.audioNodes.llpw[0]);
console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> llpw");
} catch (e) { }
break;
case 'n0':
try { CiderAudioRenderer.audioNodes.atmosphereRealizer2.connect(CiderAudioRenderer.context.destination); console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> destination");} catch (e) { }
break;
}
}
},
atmosphereRealizer1_n5: function (status, destination) {
if (status === true) {
CiderAudioRenderer.audioNodes.atmosphereRealizer1 = CiderAudioRenderer.context.createConvolver();
CiderAudioRenderer.audioNodes.atmosphereRealizer1.normalize = false;
let atmosphereRealizerProfile = CiderAudioRenderer.atmosphereRealizerProfiles.find(function (profile) {
return profile.id === app.cfg.audio.maikiwiAudio.atmosphereRealizer1_value;
});
if (atmosphereRealizerProfile === undefined) {
atmosphereRealizerProfile = CiderAudioRenderer.atmosphereRealizerProfiles[0];
}
fetch(atmosphereRealizerProfile.file).then(async (impulseData) => {
let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudioRenderer.audioNodes.atmosphereRealizer1.buffer = await CiderAudioRenderer.context.decodeAudioData(bufferedImpulse);
});
switch (destination) {
case "spatial":
try { CiderAudioRenderer.audioNodes.atmosphereRealizer1.connect(CiderAudioRenderer.audioNodes.spatialNode); console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> Spatial");} catch (e) { }
break;
case "n6":
try {
CiderAudioRenderer.audioNodes.atmosphereRealizer1.connect(CiderAudioRenderer.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n5':
try {
CiderAudioRenderer.audioNodes.atmosphereRealizer1.connect(CiderAudioRenderer.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> atmosphereRealizer1");
} catch (e) { }
break;
case 'n4':
try {
CiderAudioRenderer.audioNodes.atmosphereRealizer1.connect(CiderAudioRenderer.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> vibrantbassNode");
} catch (e) { }
break;
case 'n3':
try {
CiderAudioRenderer.audioNodes.atmosphereRealizer1.connect(CiderAudioRenderer.audioNodes.audioBands[0]);
console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> audioBands");
} catch (e) { }
break;
case 'n2':
try {
CiderAudioRenderer.audioNodes.atmosphereRealizer1.connect(CiderAudioRenderer.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> opportunisticCorrection");
} catch (e) { }
break;
case 'n1':
try {
CiderAudioRenderer.audioNodes.atmosphereRealizer1.connect(CiderAudioRenderer.audioNodes.llpw[0]);
console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> llpw");
} catch (e) { }
break;
case 'n0':
try { CiderAudioRenderer.audioNodes.atmosphereRealizer1.connect(CiderAudioRenderer.context.destination); console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> destination");} catch (e) { }
break;
}
}
},
opportunisticCorrection_n2: function (status, destination) {
if (status === true) {
CiderAudioRenderer.audioNodes.opportunisticCorrection = CiderAudioRenderer.context.createConvolver();
CiderAudioRenderer.audioNodes.opportunisticCorrection.normalize = false;
let opportunisticCorrectionProfile = CiderAudioRenderer.opportunisticCorrectionProfiles.find(function (profile) {
return profile.id === app.cfg.audio.maikiwiAudio.opportunisticCorrection_state;
});
if (opportunisticCorrectionProfile === undefined) {
opportunisticCorrectionProfile = CiderAudioRenderer.opportunisticCorrectionProfiles[0];
}
fetch(opportunisticCorrectionProfile.file).then(async (impulseData) => {
let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudioRenderer.audioNodes.opportunisticCorrection.buffer = await CiderAudioRenderer.context.decodeAudioData(bufferedImpulse);
});
switch (destination) {
case "spatial":
try { CiderAudioRenderer.audioNodes.opportunisticCorrection.connect(CiderAudioRenderer.audioNodes.spatialNode); console.debug("[Cider][Audio] opportunisticCorrection_n2 -> Spatial");} catch (e) { }
break;
case "n6":
try {
CiderAudioRenderer.audioNodes.opportunisticCorrection.connect(CiderAudioRenderer.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] opportunisticCorrection_n2 -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n5':
try {
CiderAudioRenderer.audioNodes.opportunisticCorrection.connect(CiderAudioRenderer.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] opportunisticCorrection_n2 -> atmosphereRealizer1");
} catch (e) { }
break;
case 'n4':
try { CiderAudioRenderer.audioNodes.opportunisticCorrection.connect(CiderAudioRenderer.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] opportunisticCorrection_n2 -> vibrantbassNode");} catch (e) { }
break;
case 'n3':
try { CiderAudioRenderer.audioNodes.opportunisticCorrection.connect(CiderAudioRenderer.audioNodes.audioBands[0]); console.debug("[Cider][Audio] opportunisticCorrection_n2 -> audioBands");} catch (e) { }
break;
case 'n2':
try {
CiderAudioRenderer.audioNodes.opportunisticCorrection.connect(CiderAudioRenderer.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] opportunisticCorrection_n2 -> opportunisticCorrection");
} catch (e) { }
break;
case 'n1':
try {
CiderAudioRenderer.audioNodes.opportunisticCorrection.connect(CiderAudioRenderer.audioNodes.opportunisticCorrection[0]);
console.debug("[Cider][Audio] opportunisticCorrection_n2 -> opportunisticCorrection");
} catch (e) { }
break;
case 'n0':
try { CiderAudioRenderer.audioNodes.opportunisticCorrection.connect(CiderAudioRenderer.context.destination); console.debug("[Cider][Audio] opportunisticCorrection_n2 -> destination");} catch (e) { }
break;
}
}
},
llpw_n1: function (status, destination) {
if (status === true) {
let c_LLPW_Q = [1.250, 0.131, 10, 2.5, 2.293, 0.110, 14.14, 1.552, 28.28, 7.071, 2.847, 5, 0.625, 7.071, 3.856, 3.856, 20, 28.28, 20, 14.14, 2.102, 6.698, 3.536, 10];
let c_LLPW_GAIN = [-0.11, 0.27, -0.8, 0.57, 1.84, -0.38, 0.47, -1.56, 0.83, 1.58, -1.79, -0.45, 0.48, 1.22, -1.58, -1.59, -2.03, 2.56, -2.2, -2.48, 4.75, 10.5, 1.43, 3.76];
let c_LLPW_FREQUENCIES = [400.83, 5812.8, 8360, 10413, 10658, 12079, 12899, 13205, 14848, 15591, 15778, 15783, 16716, 16891, 17255, 17496, 18555, 18622, 19219, 19448, 19664, 21341, 21353, 22595];
let LLPW_Q = [5, 1, 3.536, 1.25, 8.409, 1.25, 14.14, 7.071, 5, 0.625, 16.82, 20, 20, 20, 28.28, 28.28, 28.28, 20, 33.64, 33.64, 10, 28.28, 7.071, 3.856];
let LLPW_GAIN = [0.38, -1.81, -0.23, -0.51, 0.4, 0.84, 0.36, -0.34, 0.27, -1.2, -0.42, -0.67, 0.81, 1.31, -0.71, 0.68, -1.04, 0.79, -0.73, -1.33, 1.17, 0.57, 0.35, 6.33];
let LLPW_FREQUENCIES = [16.452, 24.636, 37.134, 74.483, 159.54, 308.18, 670.21, 915.81, 1200.7, 2766.4, 2930.6, 4050.6, 4409.1, 5395.2, 5901.6, 6455.5, 7164.1, 7724.1, 8449, 10573, 12368, 14198, 17910, 18916];
CiderAudioRenderer.audioNodes.llpw = []
switch (app.cfg.audio.maikiwiAudio.ciderPPE_value) {
case "MAIKIWI":
try {
switch (localStorage.getItem("playingBitrate")) {
case "64":
CiderAudioRenderer.audioNodes.llpw[0] = CiderAudioRenderer.context.createConvolver();
CiderAudioRenderer.audioNodes.llpw[0].normalize = false;
fetch('./cideraudio/impulses/CAP_64.wav').then(async (impulseData) => {
let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudioRenderer.audioNodes.llpw[0].buffer = await CiderAudioRenderer.context.decodeAudioData(bufferedImpulse);
});
console.debug("[Cider][Audio] CAP Adaptive - 64kbps");
break;
case "256":
CiderAudioRenderer.audioNodes.llpw[0] = CiderAudioRenderer.context.createConvolver(); CiderAudioRenderer.audioNodes.llpw[0].normalize = false;
CiderAudioRenderer.audioNodes.llpw[1] = CiderAudioRenderer.context.createGain(); CiderAudioRenderer.audioNodes.llpw[1].gain.value = 2.37; // Post Gain Compensation
CiderAudioRenderer.audioNodes.llpw[0].connect(CiderAudioRenderer.audioNodes.llpw[1]);
fetch('./cideraudio/impulses/CAP_256_FINAL_48k.wav').then(async (impulseData) => {
let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudioRenderer.audioNodes.llpw[0].buffer = await CiderAudioRenderer.context.decodeAudioData(bufferedImpulse);
});
console.debug("[Cider][Audio] CAP Adaptive - 256kbps");
break;
default:
CiderAudioRenderer.audioNodes.llpw[0] = CiderAudioRenderer.context.createGain(); CiderAudioRenderer.audioNodes.llpw[0].gain.value = 1
console.debug("[Cider][Audio] CAP Disabled (Passthrough) : Non-Lossy Bitrate.");
break;
}
} catch (e) {
CiderAudioRenderer.audioNodes.llpw[0] = CiderAudioRenderer.context.createConvolver(); CiderAudioRenderer.audioNodes.llpw[0].normalize = false;
CiderAudioRenderer.audioNodes.llpw[1] = CiderAudioRenderer.context.createGain(); CiderAudioRenderer.audioNodes.llpw[1].gain.value = 2.37;
CiderAudioRenderer.audioNodes.llpw[0].connect(CiderAudioRenderer.audioNodes.llpw[1]);
fetch('./cideraudio/impulses/CAP_256_FINAL_48k.wav').then(async (impulseData) => {
let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudioRenderer.audioNodes.llpw[0].buffer = await CiderAudioRenderer.context.decodeAudioData(bufferedImpulse);
});
console.debug("[Cider][Audio] CAP Adaptive - (Error Fallback) 256kbps");
}
break;
case "MAIKIWI_LEGACY":
CiderAudioRenderer.audioNodes.llpw[0] = CiderAudioRenderer.context.createConvolver();
CiderAudioRenderer.audioNodes.llpw[0].normalize = false;
fetch('./cideraudio/impulses/CAP_Maikiwi.wav').then(async (impulseData) => {
let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudioRenderer.audioNodes.llpw[0].buffer = await CiderAudioRenderer.context.decodeAudioData(bufferedImpulse);
});
console.debug("[Cider][Audio] CAP - Maikiwi Signature Mode");
break;
case "NATURAL":
CiderAudioRenderer.audioNodes.llpw[0] = CiderAudioRenderer.context.createConvolver();
CiderAudioRenderer.audioNodes.llpw[0].normalize = false;
fetch('./cideraudio/impulses/CAP_Natural.wav').then(async (impulseData) => {
let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudioRenderer.audioNodes.llpw[0].buffer = await CiderAudioRenderer.context.decodeAudioData(bufferedImpulse);
});
console.debug("[Cider][Audio] CAP - Natural Mode");
break;
case "LEGACY":
for (let i = 0; i < LLPW_FREQUENCIES.length; i++) {
CiderAudioRenderer.audioNodes.llpw[i] = CiderAudioRenderer.context.createBiquadFilter();
CiderAudioRenderer.audioNodes.llpw[i].type = 'peaking'; // 'peaking';
CiderAudioRenderer.audioNodes.llpw[i].frequency.value = LLPW_FREQUENCIES[i];
CiderAudioRenderer.audioNodes.llpw[i].Q.value = LLPW_Q[i];
CiderAudioRenderer.audioNodes.llpw[i].gain.value = LLPW_GAIN[i];
}
for (let i = 1; i < LLPW_FREQUENCIES.length; i ++) {
CiderAudioRenderer.audioNodes.llpw[i-1].connect(CiderAudioRenderer.audioNodes.llpw[i]);
}
console.debug("[Cider][Audio] CAP - Legacy Mode");
break;
default:
CiderAudioRenderer.audioNodes.llpw[0] = CiderAudioRenderer.context.createConvolver(); CiderAudioRenderer.audioNodes.llpw[0].normalize = false;
CiderAudioRenderer.audioNodes.llpw[1] = CiderAudioRenderer.context.createGain(); CiderAudioRenderer.audioNodes.llpw[1].gain.value = 2.57;
CiderAudioRenderer.audioNodes.llpw[0].connect(CiderAudioRenderer.audioNodes.llpw[1]);
fetch('./cideraudio/impulses/CAP_256_FINAL_48k.wav').then(async (impulseData) => {
let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudioRenderer.audioNodes.llpw[0].buffer = await CiderAudioRenderer.context.decodeAudioData(bufferedImpulse);
});
app.cfg.audio.maikiwiAudio.ciderPPE_value = "MAIKIWI";
console.debug("[Cider][Audio] CAP - Maikiwi Adaptive Mode (Defaulted from broki config)");
break;
}
switch (destination) {
case "spatial":
try { CiderAudioRenderer.audioNodes.llpw.at(-1).connect(CiderAudioRenderer.audioNodes.spatialNode); console.debug("[Cider][Audio] llpw_n1 -> Spatial");} catch (e) { }
break;
case "n6":
try {
CiderAudioRenderer.audioNodes.llpw.at(-1).connect(CiderAudioRenderer.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n5':
try {
CiderAudioRenderer.audioNodes.llpw.at(-1).connect(CiderAudioRenderer.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer1");
} catch (e) { }
break;
case 'n4':
try { CiderAudioRenderer.audioNodes.llpw.at(-1).connect(CiderAudioRenderer.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] llpw_n1 -> vibrantbassNode");} catch (e) { }
break;
case 'n3':
try { CiderAudioRenderer.audioNodes.llpw.at(-1).connect(CiderAudioRenderer.audioNodes.audioBands[0]); console.debug("[Cider][Audio] llpw_n1 -> audioBands");} catch (e) { }
break;
case 'n2':
try {
CiderAudioRenderer.audioNodes.llpw.at(-1).connect(CiderAudioRenderer.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] llpw_n1 -> opportunisticCorrection");
} catch (e) { }
break;
case 'n1':
try {
CiderAudioRenderer.audioNodes.llpw.at(-1).connect(CiderAudioRenderer.audioNodes.llpw[0]);
console.debug("[Cider][Audio] llpw_n1 -> llpw");
} catch (e) { }
break;
case 'n0':
try { CiderAudioRenderer.audioNodes.llpw.at(-1).connect(CiderAudioRenderer.context.destination); console.debug("[Cider][Audio] llpw_n1 -> destination");} catch (e) { }
break;
}
}
},
vibrantbass_n4: function (status, destination) {
if (status === true) {
let VIBRANTBASSBANDS = app.cfg.audio.maikiwiAudio.vibrantBass.frequencies;
let VIBRANTBASSGAIN = app.cfg.audio.maikiwiAudio.vibrantBass.gain;
let VIBRANTBASSQ = app.cfg.audio.maikiwiAudio.vibrantBass.Q;
CiderAudioRenderer.audioNodes.vibrantbassNode = []
for (let i = 0; i < VIBRANTBASSBANDS.length; i++) {
CiderAudioRenderer.audioNodes.vibrantbassNode[i] = CiderAudioRenderer.context.createBiquadFilter();
CiderAudioRenderer.audioNodes.vibrantbassNode[i].type = 'peaking'; // 'peaking';
CiderAudioRenderer.audioNodes.vibrantbassNode[i].frequency.value = VIBRANTBASSBANDS[i];
CiderAudioRenderer.audioNodes.vibrantbassNode[i].Q.value = VIBRANTBASSQ[i];
CiderAudioRenderer.audioNodes.vibrantbassNode[i].gain.value = VIBRANTBASSGAIN[i] * (app.cfg.audio.equalizer.vibrantBass / 10);
}
for (let i = 1; i < VIBRANTBASSBANDS.length; i++) {
CiderAudioRenderer.audioNodes.vibrantbassNode[i - 1].connect(CiderAudioRenderer.audioNodes.vibrantbassNode[i]);
}
switch (destination) {
case "spatial":
try { CiderAudioRenderer.audioNodes.vibrantbassNode.at(-1).connect(CiderAudioRenderer.audioNodes.spatialNode); console.debug("[Cider][Audio] vibrantbass_n4 -> Spatial");} catch (e) { }
break;
case "n6":
try {
CiderAudioRenderer.audioNodes.vibrantbassNode.at(-1).connect(CiderAudioRenderer.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] vibrantbass_n4 -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n5':
try {
CiderAudioRenderer.audioNodes.vibrantbassNode.at(-1).connect(CiderAudioRenderer.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] vibrantbass_n4 -> atmosphereRealizer1");
} catch (e) { }
break;
case 'n4':
try {
CiderAudioRenderer.audioNodes.vibrantbassNode.at(-1).connect(CiderAudioRenderer.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] vibrantbass_n4 -> vibrantbassNode");
} catch (e) { }
break;
case 'n3':
try {
CiderAudioRenderer.audioNodes.vibrantbassNode.at(-1).connect(CiderAudioRenderer.audioNodes.audioBands[0]);
console.debug("[Cider][Audio] vibrantbass_n4 -> audioBands");
} catch (e) { }
break;
case 'n2':
try {
CiderAudioRenderer.audioNodes.vibrantbassNode.at(-1).connect(CiderAudioRenderer.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] vibrantbass_n4 -> opportunisticCorrection");
} catch (e) { }
break;
case 'n1':
try {
CiderAudioRenderer.audioNodes.vibrantbassNode.at(-1).connect(CiderAudioRenderer.audioNodes.llpw[0]);
console.debug("[Cider][Audio] vibrantbass_n4 -> llpw");
} catch (e) { }
break;
case 'n0':
try { CiderAudioRenderer.audioNodes.vibrantbassNode.at(-1).connect(CiderAudioRenderer.context.destination); console.debug("[Cider][Audio] vibrantbass_n4 -> destination");} catch (e) { }
break;
}
}
},
hierarchical_optimizer: function () {
CiderAudioRenderer.intelliGainComp_n0_0() // Calculate headroom for upcoming convolver
const normValue = CiderAudioRenderer.audioNodes.gainNode.gain.value; // Store this temporarily so we can restore later
CiderAudioRenderer.audioNodes.gainNode.gain.value = 1;
// Render and return convolved buffer
let optimizerProfile = CiderAudioRenderer.optimizerProfile.find(function (profile) {
return profile.id === 'dirac32_96'; // Hard code for now
});
return fetch(optimizerProfile.file)
.then(async (response) => await response.arrayBuffer())
.then((arrayBuffer) => CiderAudioRenderer.context.decodeAudioData(arrayBuffer))
.then((decodedBuffer) => {
const source = new AudioBufferSourceNode(renderer, {
buffer: decodedBuffer,
});
source.connect(CiderAudioRenderer.audioNodes.intelliGainComp);
switch (lastNode) {
case 'spatial':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.spatialNode);
break;
case 'n6':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.atmosphereRealizer2);
break;
case 'n5':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.atmosphereRealizer1);
break;
case 'n4':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.vibrantbassNode[0]);
break;
case 'n3':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.audioBands[0]);
break;
case 'n2':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.opportunisticCorrection);
break;
case 'n1':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.llpw[0]);
break;
}
switch (firstNode) {
case 'spatial':
CiderAudioRenderer.audioNodes.spatialNode.disconnect();
CiderAudioRenderer.audioNodes.spatialNode.connect(renderer.destination);
break;
case 'n6':
CiderAudioRenderer.audioNodes.atmosphereRealizer2.disconnect();
CiderAudioRenderer.audioNodes.atmosphereRealizer2.connect(renderer.destination);
break;
case 'n5':
CiderAudioRenderer.audioNodes.atmosphereRealizer1.disconnect();
CiderAudioRenderer.audioNodes.atmosphereRealizer1.connect(renderer.destination);
break;
case 'n4':
CiderAudioRenderer.audioNodes.vibrantbassNode.at(-1).disconnect();
CiderAudioRenderer.audioNodes.vibrantbassNode.at(-1).connect(renderer.destination);
break;
case 'n3':
CiderAudioRenderer.audioNodes.audioBands.at(-1).disconnect();
CiderAudioRenderer.audioNodes.audioBands.at(-1).connect(renderer.destination);
break;
case 'n2':
CiderAudioRenderer.audioNodes.opportunisticCorrection.disconnect();
CiderAudioRenderer.audioNodes.opportunisticCorrection.connect(renderer.destination);
break;
case 'n1':
CiderAudioRenderer.audioNodes.llpw.at(-1).disconnect();
CiderAudioRenderer.audioNodes.llpw.at(-1).connect(renderer.destination);
break;
}
source.start();
const res = renderer.startRendering().then(function(res) {
return res;
})
})
},
hierarchical_unloading: function () {
try { CiderAudioRenderer.audioNodes.spatialNode.disconnect(); CiderAudioRenderer.audioNodes.spatialNode = null} catch (e) { }
try { CiderAudioRenderer.audioNodes.gainNode.disconnect(); } catch (e) { }
try { CiderAudioRenderer.audioNodes.atmosphereRealizer2.disconnect(); CiderAudioRenderer.audioNodes.atmosphereRealizer2 = null } catch (e) { }
try { CiderAudioRenderer.audioNodes.atmosphereRealizer1.disconnect(); CiderAudioRenderer.audioNodes.atmosphereRealizer1 = null } catch (e) { }
try { for (var i of CiderAudioRenderer.audioNodes.llpw) { i.disconnect(); } CiderAudioRenderer.audioNodes.llpw = null } catch (e) { }
try { for (var i of CiderAudioRenderer.audioNodes.vibrantbassNode) { i.disconnect(); } CiderAudioRenderer.audioNodes.vibrantbassNode = null } catch (e) { }
try { for (var i of CiderAudioRenderer.audioNodes.audioBands) { i.disconnect(); } CiderAudioRenderer.audioNodes.vibrantbassNode = null} catch (e) { };
try {CiderAudioRenderer.audioNodes.opportunisticCorrection.disconnect(); CiderAudioRenderer.audioNodes.opportunisticCorrection = null } catch (e) { };
console.debug("[Cider][Audio] Finished hierarchical unloading")
},
hierarchical_loading: async function () {
if (app.cfg.audio.maikiwiAudio.staticOptimizer.lock === true) { return } // Do nothing if locked by optimizer.
const configMap = new Map([
['spatial', app.cfg.audio.maikiwiAudio.spatial === true],
['n6', app.cfg.audio.maikiwiAudio.atmosphereRealizer2 === true],
['n5', app.cfg.audio.maikiwiAudio.atmosphereRealizer1 === true],
['n4', app.cfg.audio.equalizer.vibrantBass != 0],
['n3', Math.max(...app.cfg.audio.equalizer.gain) != 0],
['n2', app.cfg.audio.maikiwiAudio.opportunisticCorrection_state !== "OFF"],
['n1', app.cfg.audio.maikiwiAudio.ciderPPE === true]
]);
CiderAudioRenderer.hierarchical_unloading();
let lastNode = 'n0'; let index = 0; let firstNode = 'n0'
for (let [tier, value] of configMap.entries()) {
if (value === true) {
if (index === 0) {firstNode = tier}
switch (tier) {
case 'spatial':
CiderAudioRenderer.spatial_ninf();
lastNode = 'spatial';
break;
case 'n6':
app.cfg.audio.normalization = true;
CiderAudioRenderer.atmosphereRealizer2_n6(true, lastNode);
lastNode = 'n6';
break;
case 'n5':
app.cfg.audio.normalization = true;
CiderAudioRenderer.atmosphereRealizer1_n5(true, lastNode);
lastNode = 'n5';
break;
case 'n4':
CiderAudioRenderer.vibrantbass_n4(true, lastNode);
lastNode = 'n4';
break;
case 'n3':
CiderAudioRenderer.equalizer(true, lastNode);
lastNode = 'n3';
break;
case 'n2':
CiderAudioRenderer.opportunisticCorrection_n2(true, lastNode);
lastNode = 'n2';
break;
case 'n1':
app.cfg.audio.normalization = true;
CiderAudioRenderer.llpw_n1(true, lastNode);
lastNode = 'n1';
break;
}
}
}
app.cfg.audio.maikiwiAudio.lastNode = lastNode; app.cfg.audio.maikiwiAudio.firstNode = firstNode; // Sync last node & first
switch (lastNode) {
case 'spatial':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.spatialNode);
console.debug("[Cider][Audio] gainNode -> Spatial");
break;
case 'n6':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] gainNode -> atmosphereRealizer2");
break;
case 'n5':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] gainNode -> atmosphereRealizer1");
break;
case 'n4':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] gainNode -> vibrantbass");
break;
case 'n3':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.audioBands[0]);
console.debug("[Cider][Audio] gainNode -> audioBands");
break;
case 'n2':
try {
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] gainNode -> opportunisticCorrection");
} catch (e) { }
break;
case 'n1':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.audioNodes.llpw[0]);
console.debug("[Cider][Audio] gainNode -> llpw");
break;
case 'n0':
CiderAudioRenderer.audioNodes.gainNode.connect(CiderAudioRenderer.context.destination);
console.debug("[Cider][Audio] gainNode -> destination");
break;
}
console.debug('[Cider][Audio]\n' + [...configMap.entries()] + '\n lastNode: ' + lastNode);
CiderAudioRenderer.intelliGainComp_n0_0();
console.debug("[Cider][Audio] Finished hierarchical loading");
},
equalizer: function (status, destination) { // n3_1
if (status === true) {
let BANDS = app.cfg.audio.equalizer.frequencies;
let GAIN = app.cfg.audio.equalizer.gain;
let Q = app.cfg.audio.equalizer.Q;
CiderAudioRenderer.audioNodes.audioBands = [];
for (let i = 0; i < BANDS.length; i++) {
CiderAudioRenderer.audioNodes.audioBands[i] = CiderAudioRenderer.context.createBiquadFilter();
CiderAudioRenderer.audioNodes.audioBands[i].type = 'peaking'; // 'peaking';
CiderAudioRenderer.audioNodes.audioBands[i].frequency.value = BANDS[i];
CiderAudioRenderer.audioNodes.audioBands[i].Q.value = Q[i];
CiderAudioRenderer.audioNodes.audioBands[i].gain.value = GAIN[i] * app.cfg.audio.equalizer.mix;
}
for (let i = 1; i < BANDS.length; i++) {
CiderAudioRenderer.audioNodes.audioBands[i - 1].connect(CiderAudioRenderer.audioNodes.audioBands[i]);
}
switch (destination) {
case 'spatial':
CiderAudioRenderer.audioNodes.audioBands.at(-1).connect(CiderAudioRenderer.audioNodes.spatialNode);
console.debug("[Cider][Audio] Equalizer -> Spatial");
break;
case "n6":
try {
CiderAudioRenderer.audioNodes.audioBands.at(-1).connect(CiderAudioRenderer.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] Equalizer -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n5':
try {
CiderAudioRenderer.audioNodes.audioBands.at(-1).connect(CiderAudioRenderer.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] Equalizer -> atmosphereRealizer1");
} catch (e) { }
break;
case 'n4':
try {
CiderAudioRenderer.audioNodes.audioBands.at(-1).connect(CiderAudioRenderer.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] Equalizer -> vibrantbassNode");
} catch (e) { }
break;
case 'n3':
try {
CiderAudioRenderer.audioNodes.audioBands.at(-1).connect(CiderAudioRenderer.audioNodes.audioBands[0]);
console.debug("[Cider][Audio] Equalizer -> audioBands");
} catch (e) { }
break;
case 'n2':
try {
CiderAudioRenderer.audioNodes.audioBands.at(-1).connect(CiderAudioRenderer.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] Equalizer -> opportunisticCorrection");
} catch (e) { }
break;
case 'n1':
try {
CiderAudioRenderer.audioNodes.audioBands.at(-1).connect(CiderAudioRenderer.audioNodes.llpw[0]);
console.debug("[Cider][Audio] Equalizer -> llpw");
} catch (e) { }
break;
case 'n0':
try { CiderAudioRenderer.audioNodes.audioBands.at(-1).connect(CiderAudioRenderer.context.destination); console.debug("[Cider][Audio] Equalizer -> destination");} catch (e) { }
break;
}
}
}
}
export { CiderAudioRenderer }

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;

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;
@ -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;
@ -2206,7 +2177,7 @@ input[type=checkbox][switch]:checked:active::before {
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;
@ -2216,7 +2187,6 @@ input[type=checkbox][switch]:checked:active::before {
font-weight: 500; font-weight: 500;
margin: 0px 4px; margin: 0px 4px;
&:after { &:after {
--dist: 1px; --dist: 1px;
content: ""; content: "";
@ -2231,10 +2201,9 @@ 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);
@ -2245,10 +2214,7 @@ input[type=checkbox][switch]:checked:active::before {
&: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);
} }
} }
@ -2265,17 +2231,15 @@ input[type=checkbox][switch]:checked:active::before {
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%);
@ -2300,7 +2264,7 @@ input[type=checkbox][switch]:checked:active::before {
} }
&:after { &:after {
content: ''; content: "";
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -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;

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,7 +39,7 @@
} }
} }
} }
.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;
} }
} }

View file

@ -1,48 +1,47 @@
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 { CiderAudioRenderer } from "../audio/cideraudiorenderer.js";
import { wsapi } from "./wsapi_interop.js" import { Events } from "./events.js";
import { MusicKitTools } from "./musickittools.js" import { wsapi } from "./wsapi_interop.js";
import { spawnMica } from "./mica.js" import { MusicKitTools } from "./musickittools.js";
import { svgIcon } from './components/svg-icon.js' import { spawnMica } from "./mica.js";
import { sidebarLibraryItem } from './components/sidebar-library-item.js' import { svgIcon } from "./components/svg-icon.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 @@
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 {
animation: menuInDown .10s var(--appleEase);
}
&.menu-panel-body-up {
animation: menuInUp .10s var(--appleEase);
}
&.menu-panel-body-down {
animation: menuInDown 0.1s var(--appleEase);
} }
@keyframes menuInUp { &.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"
:title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container"
v-b-tooltip.hover
></div> ></div>
<div class="audio-type ppe-icon" v-if="mk.nowPlayingItem.localFilesMetadata == null && cfg.audio.maikiwiAudio.ciderPPE === true" <div class="audio-type ppe-icon"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')" v-b-tooltip.hover 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>
@ -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>
@ -155,7 +178,7 @@
@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>

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