Merge branch 'ciderapp:main' into i18n

This commit is contained in:
Gabriel Davila 2022-07-19 23:07:35 -03:00 committed by GitHub
commit 464b580c81
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
117 changed files with 10725 additions and 7858 deletions

View file

@ -30,6 +30,9 @@ jobs:
key: yarn-packages-{{ checksum "cider.lock" }} key: yarn-packages-{{ checksum "cider.lock" }}
paths: paths:
- ~/.cache/yarn - ~/.cache/yarn
- run:
name: Clear Yarn Cache
command: yarn cache clean
- run: - run:
name: TypeScript Compile name: TypeScript Compile
command: yarn build command: yarn build
@ -80,6 +83,12 @@ 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
@ -105,6 +114,12 @@ 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
@ -147,6 +162,7 @@ jobs:
- run: - run:
name: Publish Release name: Publish Release
command: | command: |
echo "Creating release for Cider v${APP_VERSION} on the ${CIRCLE_BRANCH} branch."
gh release create "v${APP_VERSION}" --title "Cider Version ${APP_VERSION} (${CIRCLE_BRANCH})" --generate-notes -R ciderapp/cider-releases ~/Cider/dist/artifacts/*.deb ~/Cider/dist/artifacts/*.AppImage ~/Cider/dist/artifacts/*.snap ~/Cider/dist/artifacts/*.exe ~/Cider/dist/artifacts/*.yml ~/Cider/dist/artifacts/*.blockmap gh release create "v${APP_VERSION}" --title "Cider Version ${APP_VERSION} (${CIRCLE_BRANCH})" --generate-notes -R ciderapp/cider-releases ~/Cider/dist/artifacts/*.deb ~/Cider/dist/artifacts/*.AppImage ~/Cider/dist/artifacts/*.snap ~/Cider/dist/artifacts/*.exe ~/Cider/dist/artifacts/*.yml ~/Cider/dist/artifacts/*.blockmap
# Orchestrate our job run sequence # Orchestrate our job run sequence

View file

@ -94,6 +94,9 @@ jobs:
yarn install yarn install
cp resources/verror-types node_modules/@types/verror/index.d.ts cp resources/verror-types node_modules/@types/verror/index.d.ts
cp resources/macPackager.js node_modules/app-builder-lib/out/macPackager.js 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 yarn dist:universalNotWorking -p never
# - name: Perform CodeQL Analysis # - name: Perform CodeQL Analysis
# uses: github/codeql-action/analyze@v1 # uses: github/codeql-action/analyze@v1

3
.gitignore vendored
View file

@ -3,6 +3,7 @@ dist
build build
.flatpak* .flatpak*
yarn-cache yarn-cache
src/renderer/style.css
# Misc # Misc
.idea .idea
@ -328,3 +329,5 @@ savedconfig/cider-config.json
savedconfig/config.json savedconfig/config.json
savedconfig/session.json savedconfig/session.json
savedconfig/window-state.json savedconfig/window-state.json
src/main/base/sample.json

View file

@ -21,10 +21,10 @@
* [Documentation](https://docs.cider.sh) * [Documentation](https://docs.cider.sh)
* [Request Feature](https://github.com/ciderapp/Cider/discussions/new?category=feature-request) * [Request Feature](https://github.com/ciderapp/Cider/discussions/new?category=feature-request)
* [Report Bug](https://github.com/ciderapp/Cider/issues/new?assignees=&labels=bug&template=bug_report.md&title=%5BBUG%5D+) * [Report Bug](https://github.com/ciderapp/Cider/issues/new?assignees=&labels=bug&template=bug_report.md&title=%5BBUG%5D+)
* [**View The Releases**](https://github.com/ciderapp/Cider/releases/latest) * [**View The Releases**](https://github.com/ciderapp/cider-releases/releases/latest)
### Install Sources ### Install Sources
[![Get it from Github](https://img.shields.io/badge/Get_It_From_GitHub-100000?style=for-the-badge&logo=github&logoColor=white)](https://github.com/ciderapp/cider/releases/latest) [![Get it from Github](https://img.shields.io/badge/Get_It_From_GitHub-100000?style=for-the-badge&logo=github&logoColor=white)](https://github.com/ciderapp/cider-releases/releases/latest)
[![Get it from the Microsoft Store](https://img.shields.io/badge/Get_It_From_The_Microsoft_Store-100000?style=for-the-badge&logo=microsoft)](https://www.microsoft.com/store/apps/9P21XJ9D9G66) [![Get it from the Microsoft Store](https://img.shields.io/badge/Get_It_From_The_Microsoft_Store-100000?style=for-the-badge&logo=microsoft)](https://www.microsoft.com/store/apps/9P21XJ9D9G66)

View file

@ -14,7 +14,8 @@
"homepage": "https://cider.sh/", "homepage": "https://cider.sh/",
"buildResources": "resources", "buildResources": "resources",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc && yarn compile-less",
"compile-less": "lessc ./src/renderer/style.less ./src/renderer/style.css",
"watch": "tsc --watch", "watch": "tsc --watch",
"start": "run-script-os", "start": "run-script-os",
"start:win32": "yarn build && set ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers", "start:win32": "yarn build && set ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
@ -39,10 +40,13 @@
"dependencies": { "dependencies": {
"@sentry/electron": "^3.0.7", "@sentry/electron": "^3.0.7",
"@sentry/integrations": "^6.19.6", "@sentry/integrations": "^6.19.6",
"@types/pouchdb": "^6.4.0",
"@types/pouchdb-node": "^6.1.4",
"adm-zip": "0.4.10", "adm-zip": "0.4.10",
"airtunes2": "git+https://github.com/ciderapp/node_airtunes2", "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.16", "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.6",
@ -57,16 +61,22 @@
"jimp": "^0.16.1", "jimp": "^0.16.1",
"jsonc": "^2.0.0", "jsonc": "^2.0.0",
"lastfmapi": "^0.1.1", "lastfmapi": "^0.1.1",
"level": "^8.0.0",
"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.3", "music-metadata": "^7.12.4",
"node-gyp": "^9.0.0", "node-gyp": "^9.0.0",
"node-ssdp": "^4.0.1", "node-ssdp": "^4.0.1",
"pouchdb-adapter-leveldb": "^7.3.0",
"pouchdb-node": "^7.3.0",
"pouchdb-upsert": "^2.2.0",
"qrcode": "^1.5.0", "qrcode": "^1.5.0",
"react": "^18.0.0", "react": "^18.0.0",
"react-dom": "^18.0.0", "react-dom": "^18.0.0",
"run-script-os": "^1.1.6", "run-script-os": "^1.1.6",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"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.5.0",
@ -83,6 +93,7 @@
"electron-builder": "^23.0.3", "electron-builder": "^23.0.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",
"musickit-typescript": "^1.2.4", "musickit-typescript": "^1.2.4",
"typescript": "^4.6.4", "typescript": "^4.6.4",
"vue-devtools": "^5.1.4", "vue-devtools": "^5.1.4",
@ -111,9 +122,9 @@
} }
], ],
"build": { "build": {
"electronVersion": "18.3.3", "electronVersion": "18.3.5",
"electronDownload": { "electronDownload": {
"version": "18.3.3+wvcus", "version": "18.3.5+wvcus",
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v" "mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
}, },
"appId": "cider", "appId": "cider",

View file

@ -24,8 +24,9 @@ exports.default = function(context) {
// 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'))
// execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac-x64',{stdio: 'inherit'}) if (fs.existsSync('dist/mac-x64') || fs.existsSync('dist/mac-universal--x64') )
execSync('cd ./node_modules/cider_utils; yarn run prebuild-downloads --platform=darwin --arch=arm64 --verbose; cd ../..',{stdio: 'inherit'})
// console.log('VMP signing complete') // console.log('VMP signing complete')

View file

@ -3,7 +3,6 @@
LATEST_SHA=$(curl -s https://api.github.com/repos/ciderapp/Cider/branches/stable | grep sha | cut -d '"' -f 4 | sed 's/v//' | xargs) LATEST_SHA=$(curl -s https://api.github.com/repos/ciderapp/Cider/branches/stable | grep sha | cut -d '"' -f 4 | sed 's/v//' | xargs)
COMMITSINCESTABLE=$(git rev-list $LATEST_SHA..HEAD --count) COMMITSINCESTABLE=$(git rev-list $LATEST_SHA..HEAD --count)
CURRENT_VERSION=$(node -p -e "require('./package.json').version") CURRENT_VERSION=$(node -p -e "require('./package.json').version")
CIRCLE_BRANCH="main"
if [[ $CIRCLE_BRANCH == "main" && $COMMITSINCESTABLE -gt 0 ]]; then if [[ $CIRCLE_BRANCH == "main" && $COMMITSINCESTABLE -gt 0 ]]; then
NEW_VERSION="${CURRENT_VERSION}-beta.${COMMITSINCESTABLE}" NEW_VERSION="${CURRENT_VERSION}-beta.${COMMITSINCESTABLE}"
else else

View file

@ -497,3 +497,27 @@ Update 14/06/2022 14:10 UTC
* `term.themeManaged`: Added to `en_US` * `term.themeManaged`: Added to `en_US`
Update 15/06/2022 20:00 UTC
* `settings.notyf.connectivity.lastfmScrobble.connectError`: Added to `en_US`
* `settings.notyf.connectivity.lastfmScrobble.connectSuccess`: Added to `en_US`
* `settings.notyf.connectivity.lastfmScrobble.connecting`: Added to `en_US`
Update 19/06/2022 12:00 UTC
* `settings.option.connectivity.lastfmScrobble.filterLoop.description`: Added to `en_US`
Update 21/06/2022 20:39 UTC
* `term.showSearch`: Added to `en_US`
* `term.hideSearch`: Added to `en_US`
Update 23/06/2022 04:00 UTC
* `settings.option.connectivity.lastfmScrobble.filterTypes`: Added to `en_US`
Update 03/07/2022 20:00 UTC
* `term.plugins`: Added to `en_US`
* `settings.header.visual.styles`: Added to `en_US`

View file

@ -386,5 +386,18 @@
"term.track": { "term.track": {
"one": "Titel", "one": "Titel",
"other": "Titel" "other": "Titel"
} },
"settings.option.visual.customAccentColor": "Benutzerdefinierte Akzentfarbe",
"settings.option.visual.accentColor": "Akzentfarbe",
"settings.option.visual.purplePodcastPlaybackBar": "Lila Wiedergabeleiste für Podcasts",
"settings.option.visual.windowColor": "Fenstertönung Farbe",
"action.cut": "Ausschneiden",
"action.paste": "Einfügen",
"action.selectAll": "Alles auswählen",
"action.delete": "Löschen",
"home.syncFavorites": "Sync Favoriten",
"term.quit" : "Beenden",
"settings.option.connectivity.lastfmScrobble.filterLoop.description": "Verhindert, dass geloopte Titel gescrobbelt oder in der (Hört Gerade)-Liste auf Last.fm angezeigt werden",
"settings.option.connectivity.lastfmScrobble.filterTypes": "Medientypen filtern (Last.fm)",
"settings.option.connectivity.lastfmScrobble.manualToken": "Last.fm-Token manuell eingeben"
} }

View file

@ -22,7 +22,6 @@
"term.logout": "Wogout", "term.logout": "Wogout",
"term.login": "Wog In", "term.login": "Wog In",
"term.quickNav": "Quick Nyav", "term.quickNav": "Quick Nyav",
"term.cast": "Cast",
"term.about": "About", "term.about": "About",
"term.privateSession": "Pwivate Session", "term.privateSession": "Pwivate Session",
"term.disablePrivateSession": "Disabwe Pwivate Session", "term.disablePrivateSession": "Disabwe Pwivate Session",
@ -42,6 +41,7 @@
"term.artists": "Awtists", "term.artists": "Awtists",
"term.podcasts": "Podcasts", "term.podcasts": "Podcasts",
"term.playlists": "Pwaywists", "term.playlists": "Pwaywists",
"term.charts": "Chawts",
"term.playlist": "Pwaywist", "term.playlist": "Pwaywist",
"term.newPlaylist": "Nyew Pwaywist", "term.newPlaylist": "Nyew Pwaywist",
"term.newPlaylistFolder": "Nyew Pwaywist Fowdew", "term.newPlaylistFolder": "Nyew Pwaywist Fowdew",
@ -51,6 +51,7 @@
"term.navigateBack": "Nyavigate back", "term.navigateBack": "Nyavigate back",
"term.navigateForward": "Nyavigate fowwawd", "term.navigateForward": "Nyavigate fowwawd",
"term.play": "Pway", "term.play": "Pway",
"term.playpause": "Pway/Pause",
"term.pause": "Pause", "term.pause": "Pause",
"term.stop": "Stop", "term.stop": "Stop",
"term.previous": "Pwevious", "term.previous": "Pwevious",
@ -135,7 +136,7 @@
"term.amLive": "Appwe Music Wive", "term.amLive": "Appwe Music Wive",
"term.language": "Wanguage", "term.language": "Wanguage",
"term.funLanguages": "Fun", "term.funLanguages": "Fun",
"term.noLyrics": "Woading... / Wywics nyot found./ Instwumentaw.", "term.noLyrics": ">w< Sowwy Wowwy.. N-Nyo Wywics Avaiwabwe",
"term.copyright": "Copywight", "term.copyright": "Copywight",
"term.rightsReserved": "Aww Wights Wesewved.", "term.rightsReserved": "Aww Wights Wesewved.",
"term.sponsor": "Sponsow this pwoject", "term.sponsor": "Sponsow this pwoject",
@ -153,6 +154,7 @@
}, },
"term.videos": "Videos", "term.videos": "Videos",
"term.menu": "Menyu", "term.menu": "Menyu",
"term.themeManaged": "Manyaged by a theme",
"term.check": "Check", "term.check": "Check",
"term.aboutArtist": "About {{artistName}}", "term.aboutArtist": "About {{artistName}}",
"term.topResult": "Top Wesuwt", "term.topResult": "Top Wesuwt",
@ -192,6 +194,16 @@
"term.confirmLogout": "Awe you suwe you want to wogout?", "term.confirmLogout": "Awe you suwe you want to wogout?",
"term.creditDesignedBy": "Designyed by ${authorUsername}", "term.creditDesignedBy": "Designyed by ${authorUsername}",
"term.discNumber": "Disc ${discNumber}", "term.discNumber": "Disc ${discNumber}",
"term.reload": "Wewoad Cidew ?",
"term.toggleprivate": "Toggwe Pwivate Session",
"term.webremote": "Web Wemote",
"term.cast": "Cast",
"term.cast2": "Cast to Devices",
"term.quit": "Quit",
"term.zoomin": "Zoom In",
"term.zoomout": "Zoom Out",
"term.zoomreset": "Weset Zoom",
"term.fullscreen": "Fuwwscween",
"home.title": "Home", "home.title": "Home",
"home.recentlyPlayed": "Wecentwy Pwayed", "home.recentlyPlayed": "Wecentwy Pwayed",
"home.recentlyAdded": "Wecentwy Added", "home.recentlyAdded": "Wecentwy Added",
@ -264,11 +276,7 @@
"action.export": "Expowt", "action.export": "Expowt",
"action.showAlbum": "Show Compwete Awbum", "action.showAlbum": "Show Compwete Awbum",
"action.tray.minimize": "Minyimize to Tway", "action.tray.minimize": "Minyimize to Tway",
"action.tray.quit": "Quit",
"action.tray.show": "Show Cidew", "action.tray.show": "Show Cidew",
"action.tray.playpause": "Pway/Pause",
"action.tray.next": "Nyext",
"action.tray.previous": "Pwevious",
"action.tray.listento": "Wisten To:", "action.tray.listento": "Wisten To:",
"action.update": "Update", "action.update": "Update",
"action.install": "Instaww", "action.install": "Instaww",
@ -288,45 +296,26 @@
"action.createNew": "Cweate Nyew...", "action.createNew": "Cweate Nyew...",
"action.openArtworkInBrowser": "Open awtwowk in bwowsew", "action.openArtworkInBrowser": "Open awtwowk in bwowsew",
"action.scrollToTop": "Scwoww to top", "action.scrollToTop": "Scwoww to top",
"menubar.options.about": "About", "menubar.options.view": "View",
"menubar.options.settings": "Settings",
"menubar.options.quit": "Quit Cidew",
"menubar.options.view": "View ",
"menubar.options.reload": "Wewoad", "menubar.options.reload": "Wewoad",
"menubar.options.forcereload": "Fowce Wewoad", "menubar.options.forcereload": "Fowce Wewoad",
"menubar.options.toggledevtools": "Toggwe Devewopew Toows", "menubar.options.toggledevtools": "Toggwe Devewopew Toows",
"menubar.options.window": "Window", "menubar.options.window": "Window",
"menubar.options.minimize": "Minyimize", "menubar.options.minimize": "Minyimize",
"menubar.options.toggleprivate": "Toggwe Pwivate Session",
"menubar.options.webremote": "Web Wemote",
"menubar.options.audio": "Audio Settings",
"menubar.options.plugins": "Pwu-gins Menyu", "menubar.options.plugins": "Pwu-gins Menyu",
"menubar.options.controls": "Contwows", "menubar.options.controls": "Contwows",
"menubar.options.next": "Nyext",
"menubar.options.playpause": "Pway/Pause",
"menubar.options.previous": "Pwevious",
"menubar.options.volumeup": "Vowume Up", "menubar.options.volumeup": "Vowume Up",
"menubar.options.volumedown": "Vowume Down", "menubar.options.volumedown": "Vowume Down",
"menubar.options.browse": "Bwowse",
"menubar.options.artists": "Awtists",
"menubar.options.search": "Seawch",
"menubar.options.albums": "Awbums",
"menubar.options.cast": "Cast To Devices",
"menubar.options.account": "Account", "menubar.options.account": "Account",
"menubar.options.accountsettings": "Account Settings",
"menubar.options.signout": "Sign Out", "menubar.options.signout": "Sign Out",
"menubar.options.support": "Suppowt", "menubar.options.support": "Suppowt",
"menubar.options.discord": "Discowd",
"menubar.options.github": "GitHub Wiki",
"menubar.options.report": "Wepowt a...", "menubar.options.report": "Wepowt a...",
"menubar.options.bug": "Bug", "menubar.options.bug": "Bug",
"menubar.options.feature": "Featuwe Wequest", "menubar.options.feature": "Featuwe Wequest",
"menubar.options.trans": "Twanswation Wepowt/Wequest", "menubar.options.trans": "Twanswation Wepowt/Wequest",
"menubar.options.license": "View Wicense", "menubar.options.license": "View Wicense",
"menubar.options.conf": "Open Configuwation Fiwe in Editow", "menubar.options.conf": "Open Configuwation Fiwe in Editow",
"menubar.options.listennow": "Wisten Nyow", "menubar.options.zoom": "Zoom",
"menubar.options.recentlyAdded": "Wecentwy Added",
"menubar.options.songs": "Songs",
"settings.header.general": "Genyewaw", "settings.header.general": "Genyewaw",
"settings.header.general.description": "Adjust the genyewaw settings fow Cidew.", "settings.header.general.description": "Adjust the genyewaw settings fow Cidew.",
"settings.option.general.language": "Wanguage", "settings.option.general.language": "Wanguage",
@ -346,11 +335,15 @@
"settings.option.general.customizeSidebar": "Customize Sidebaw Items", "settings.option.general.customizeSidebar": "Customize Sidebaw Items",
"settings.option.general.customizeSidebar.customize": "Customize", "settings.option.general.customizeSidebar.customize": "Customize",
"settings.option.general.keybindings": "Keybindings", "settings.option.general.keybindings": "Keybindings",
"settings.option.general.keybindings.library": "Wibwawy",
"settings.option.general.keybindings.session": "Session",
"settings.option.general.keybindings.control": "Contwows",
"settings.option.general.keybindings.interface": "Intewface",
"settings.option.general.keybindings.advanced": "Advanced",
"settings.option.general.keybindings.pressCombination": "Pwess a combinyation of two keys to update keybind.", "settings.option.general.keybindings.pressCombination": "Pwess a combinyation of two keys to update keybind.",
"settings.option.general.keybindings.pressEscape": "Pwess Escape key to go back.", "settings.option.general.keybindings.pressEscape": "Pwess Escape key to go back.",
"settings.notyf.general.keybindings.update.success": "Keybind updated successfuwwy", "settings.notyf.general.keybindings.update.success": "Keybind updated successfuwwy",
"settings.prompt.general.keybindings.update.success": "Keybind updated successfuwwy. Pwess OK to wewaunch Cidew", "settings.prompt.general.keybindings.update.success": "Keybind updated successfuwwy. Pwess OK to wewaunch Cidew",
"settings.option.general.keybindings.open": "Open",
"settings.option.general.themeUpdateNotification": "Automaticawwy check fow theme updates", "settings.option.general.themeUpdateNotification": "Automaticawwy check fow theme updates",
"settings.option.general.showLovedTracksInline": "Show wuvd twacks inwinye", "settings.option.general.showLovedTracksInline": "Show wuvd twacks inwinye",
"settings.description.search": "Seawch", "settings.description.search": "Seawch",

View file

@ -103,7 +103,7 @@
"term.recentStations": "recent pisses", "term.recentStations": "recent pisses",
"term.language": "piss around the world", "term.language": "piss around the world",
"term.funLanguages": "piss languages", "term.funLanguages": "piss languages",
"term.noLyrics": "piss…", "term.noLyrics": "out of piss...",
"term.copyright": "copypiss", "term.copyright": "copypiss",
"term.rightsReserved": "all piss reserved.", "term.rightsReserved": "all piss reserved.",
"term.sponsor": "piss on Cider", "term.sponsor": "piss on Cider",

View file

@ -31,6 +31,12 @@
"term.miniplayer": "MiniPlayer", "term.miniplayer": "MiniPlayer",
"term.history": "History", "term.history": "History",
"term.search": "Search", "term.search": "Search",
"term.scroll": "Scroll Mode",
"term.scroll.infinite": "Infinite",
"term.scroll.paged": "${songsPerPage} per page",
"term.live": "LIVE",
"term.showSearch": "Show search bar",
"term.hideSearch": "Hide search bar",
"term.library": "Library", "term.library": "Library",
"term.listenNow": "Listen Now", "term.listenNow": "Listen Now",
"term.browse": "Browse", "term.browse": "Browse",
@ -134,10 +140,9 @@
"term.recentStations": "Recent Stations", "term.recentStations": "Recent Stations",
"term.personalStations": "Personal Stations", "term.personalStations": "Personal Stations",
"term.amLive": "Apple Music Live", "term.amLive": "Apple Music Live",
"term.live": "LIVE",
"term.language": "Language", "term.language": "Language",
"term.funLanguages": "Fun", "term.funLanguages": "Fun",
"term.noLyrics": "Loading... / Lyrics not found./ Instrumental.", "term.noLyrics": "Instrumental Track / No Lyrics.",
"term.copyright": "Copyright", "term.copyright": "Copyright",
"term.rightsReserved": "All Rights Reserved.", "term.rightsReserved": "All Rights Reserved.",
"term.sponsor": "Sponsor this project", "term.sponsor": "Sponsor this project",
@ -179,8 +184,9 @@
"term.top": "Top", "term.top": "Top",
"term.version": "Version", "term.version": "Version",
"term.noVideos": "No videos found.", "term.noVideos": "No videos found.",
"term.plugin": "Plug-in", "term.plugins": "Plugins",
"term.pluginMenu": "Plug-in Menu", "term.plugin": "Plugin",
"term.pluginMenu": "Plugins Menu",
"term.pluginMenu.none": "No interactive plugins", "term.pluginMenu.none": "No interactive plugins",
"term.replay": "Replay", "term.replay": "Replay",
"term.uniqueAlbums": "Unique Albums", "term.uniqueAlbums": "Unique Albums",
@ -195,7 +201,7 @@
"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",
@ -205,8 +211,12 @@
"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",
"home.syncFavorites": "Sync Favorites", "home.syncFavorites": "Sync Favorites",
"home.syncFavorites.gettingArtists": "Getting Favorited Artists...", "home.syncFavorites.gettingArtists": "Getting Favorited Artists...",
"action.favorite": "Favorite",
"action.removeFavorite": "Remove Favorite",
"action.refresh": "Refresh",
"home.title": "Home", "home.title": "Home",
"home.recentlyPlayed": "Recently Played", "home.recentlyPlayed": "Recently Played",
"home.recentlyAdded": "Recently Added", "home.recentlyAdded": "Recently Added",
@ -227,8 +237,6 @@
"podcast.episodes": "Episodes", "podcast.episodes": "Episodes",
"podcast.playEpisode": "Play Episode", "podcast.playEpisode": "Play Episode",
"podcast.website": "Podcast Website", "podcast.website": "Podcast Website",
"action.favorite": "Favorite",
"action.removeFavorite": "Remove Favorite",
"action.hideLibrary": "Hide Library", "action.hideLibrary": "Hide Library",
"action.showLibrary": "Show Library", "action.showLibrary": "Show Library",
"action.cut": "Cut", "action.cut": "Cut",
@ -237,6 +245,7 @@
"action.delete": "Delete", "action.delete": "Delete",
"action.edit": "Edit", "action.edit": "Edit",
"action.done": "Done", "action.done": "Done",
"action.submit": "Submit",
"action.editTracklist": "Edit Tracklist", "action.editTracklist": "Edit Tracklist",
"action.addToLibrary": "Add to Library", "action.addToLibrary": "Add to Library",
"action.addToLibrary.success": "Added to Library", "action.addToLibrary.success": "Added to Library",
@ -411,9 +420,9 @@
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E168_1": "Jasmine Macchiato", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E168_1": "Jasmine Macchiato",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z3600": "Hokkaido Milk Tea", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z3600": "Hokkaido Milk Tea",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500A": "Moonlight Softcake", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500A": "Moonlight Softcake",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.BSCBM": "Brown Sugar Creme Brûlée Milk",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500B": "Clafoutis aux Cerises", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500B": "Clafoutis aux Cerises",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500C": "Uji Matcha Mochi", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500C": "Uji Matcha Mochi",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.BSCBM": "Brown Sugar Creme Brûlée Milk",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.CUDDLE": "Cuddle Warmth", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.CUDDLE": "Cuddle Warmth",
"settings.option.audio.enableAdvancedFunctionality.ciderPPE": "Cider Adrenaline Processor™", "settings.option.audio.enableAdvancedFunctionality.ciderPPE": "Cider Adrenaline Processor™",
"settings.option.audio.enableAdvancedFunctionality.ciderPPE.description": "Enhances the perceived audio quality of AAC encoded audio by using a real-time algorithm that takes advantage of both psychoacoustic models of human hearing and AAC encoding characteristics.", "settings.option.audio.enableAdvancedFunctionality.ciderPPE.description": "Enhances the perceived audio quality of AAC encoded audio by using a real-time algorithm that takes advantage of both psychoacoustic models of human hearing and AAC encoding characteristics.",
@ -447,15 +456,10 @@
"settings.header.visual": "Visual", "settings.header.visual": "Visual",
"settings.header.visual.description": "Adjust the visual settings for Cider.", "settings.header.visual.description": "Adjust the visual settings for Cider.",
"settings.option.visual.windowStyle": "Window Style", "settings.option.visual.windowStyle": "Window Style",
"settings.option.visual.customAccentColor": "Custom Accent Color",
"settings.option.visual.accentColor": "Accent Color",
"settings.option.visual.purplePodcastPlaybackBar": "Purple Playback Bar for Podcasts",
"settings.option.visual.windowColor": "Window Tint Color",
"settings.option.visual.windowBackgroundStyle": "Window Background Style", "settings.option.visual.windowBackgroundStyle": "Window Background Style",
"settings.header.visual.windowBackgroundStyle.none": "None", "settings.header.visual.windowBackgroundStyle.none": "None",
"settings.header.visual.windowBackgroundStyle.artwork": "Artwork", "settings.header.visual.windowBackgroundStyle.artwork": "Artwork",
"settings.header.visual.windowBackgroundStyle.image": "Image", "settings.header.visual.windowBackgroundStyle.image": "Image",
"settings.header.visual.windowBackgroundStyle.color": "Color Tint",
"settings.option.visual.animatedArtwork": "Animated Artwork", "settings.option.visual.animatedArtwork": "Animated Artwork",
"settings.header.visual.animatedArtwork.always": "Always", "settings.header.visual.animatedArtwork.always": "Always",
"settings.header.visual.animatedArtwork.limited": "Limited to pages and special entries", "settings.header.visual.animatedArtwork.limited": "Limited to pages and special entries",
@ -481,6 +485,7 @@
"settings.prompt.visual.theme.github.URL": "Enter the URL of the theme you want to install", "settings.prompt.visual.theme.github.URL": "Enter the URL of the theme you want to install",
"settings.prompt.visual.theme.uninstallTheme": "Are you sure you want to uninstall {{ theme }}?", "settings.prompt.visual.theme.uninstallTheme": "Are you sure you want to uninstall {{ theme }}?",
"settings.option.visual.theme.checkForUpdates": "Check for updates", "settings.option.visual.theme.checkForUpdates": "Check for updates",
"settings.header.visual.styles": "Styles",
"settings.option.visual.theme.manageStyles": "Manage Styles", "settings.option.visual.theme.manageStyles": "Manage Styles",
"settings.option.visual.theme.uninstall": "Uninstall", "settings.option.visual.theme.uninstall": "Uninstall",
"settings.option.visual.theme.viewInfo": "View Info", "settings.option.visual.theme.viewInfo": "View Info",
@ -532,6 +537,12 @@
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Enable Last.fm Now Playing", "settings.option.connectivity.lastfmScrobble.nowPlaying": "Enable Last.fm Now Playing",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Remove featuring artists from song title (Last.fm)", "settings.option.connectivity.lastfmScrobble.removeFeatured": "Remove featuring artists from song title (Last.fm)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (Last.fm)", "settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (Last.fm)",
"settings.option.connectivity.lastfmScrobble.filterLoop.description": "Prevent looped tracks from being scrobbled or displayed in the Now Playing list on Last.fm.",
"settings.option.connectivity.lastfmScrobble.filterTypes": "Filter Media Types (Last.fm)",
"settings.option.connectivity.lastfmScrobble.manualToken": "Enter Last.fm Token Manually",
"settings.notyf.connectivity.lastfmScrobble.connectError": "Last.fm Connection Timed Out",
"settings.notyf.connectivity.lastfmScrobble.connectSuccess": "Last.fm Connection Successful",
"settings.notyf.connectivity.lastfmScrobble.connecting": "Connecting to Last.fm...",
"settings.header.debug": "Debug", "settings.header.debug": "Debug",
"settings.option.debug.copy_log": "Copy logs to clipboard", "settings.option.debug.copy_log": "Copy logs to clipboard",
"settings.option.debug.openAppData": "Open Cider Folder", "settings.option.debug.openAppData": "Open Cider Folder",
@ -543,11 +554,17 @@
"settings.option.experimental.unknownPlugin.description": "Allow installation of plugins from repos other than the Cider Plugin Repository", "settings.option.experimental.unknownPlugin.description": "Allow installation of plugins from repos other than the Cider Plugin Repository",
"settings.option.experimental.compactUI": "Compact UI", "settings.option.experimental.compactUI": "Compact UI",
"settings.option.window.close_button_hide": "Close Button Should Hide the Application", "settings.option.window.close_button_hide": "Close Button Should Hide the Application",
"settings.option.window.maxElementScale": "Maximum Element Scale",
"settings.option.experimental.inline_playlists": "Inline Playlists and Albums", "settings.option.experimental.inline_playlists": "Inline Playlists and Albums",
"settings.option.advanced.playlistTrackMapping": "Playlist Track Mapping", "settings.option.advanced.playlistTrackMapping": "Playlist Track Mapping",
"settings.option.advanced.playlistTrackMapping.description": "Enables deep scanning of playlists to determine which tracks are in which playlists. Playlist cache build times can increase significantly.", "settings.option.advanced.playlistTrackMapping.description": "Enables deep scanning of playlists to determine which tracks are in which playlists. Playlist cache build times can increase significantly.",
"settings.option.visual.transparent": "Transparent frame", "settings.option.visual.transparent": "Transparent frame",
"settings.option.visual.transparent.description": "needs Theme Support, requires relaunch", "settings.option.visual.transparent.description": "needs Theme Support, requires relaunch",
"settings.option.visual.customAccentColor": "Custom Accent Color",
"settings.option.visual.accentColor": "Accent Color",
"settings.option.visual.purplePodcastPlaybackBar": "Purple Playback Bar for Podcasts",
"settings.option.visual.windowColor": "Window Tint Color",
"settings.header.visual.windowBackgroundStyle.color": "Color Tint",
"settings.header.advanced": "Advanced", "settings.header.advanced": "Advanced",
"settings.header.connect": "Sync", "settings.header.connect": "Sync",
"settings.option.connect.link_account": "Enable Sync with Cider Connect", "settings.option.connect.link_account": "Enable Sync with Cider Connect",

View file

@ -22,7 +22,6 @@
"term.logout": "Déconnexion", "term.logout": "Déconnexion",
"term.login": "Connexion", "term.login": "Connexion",
"term.quickNav": "Navigation rapide", "term.quickNav": "Navigation rapide",
"term.cast": "Diffuser",
"term.about": "À propos", "term.about": "À propos",
"term.privateSession": "Session privée", "term.privateSession": "Session privée",
"term.disablePrivateSession": "Désactiver la session privée", "term.disablePrivateSession": "Désactiver la session privée",
@ -33,7 +32,7 @@
"term.history": "Historique", "term.history": "Historique",
"term.search": "Recherche", "term.search": "Recherche",
"term.library": "Bibliothèque", "term.library": "Bibliothèque",
"term.listenNow": "Écoutez maintenant", "term.listenNow": "Écouter",
"term.browse": "Explorer", "term.browse": "Explorer",
"term.radio": "Radio", "term.radio": "Radio",
"term.recentlyAdded": "Ajouté récemment", "term.recentlyAdded": "Ajouté récemment",
@ -42,6 +41,7 @@
"term.artists": "Artistes", "term.artists": "Artistes",
"term.podcasts": "Podcasts", "term.podcasts": "Podcasts",
"term.playlists": "Playlists", "term.playlists": "Playlists",
"term.charts": "Classements",
"term.playlist": "Playlist", "term.playlist": "Playlist",
"term.newPlaylist": "Nouvelle Playlist", "term.newPlaylist": "Nouvelle Playlist",
"term.newPlaylistFolder": "Nouveau dossier de playlist", "term.newPlaylistFolder": "Nouveau dossier de playlist",
@ -51,7 +51,9 @@
"term.navigateBack": "Naviguer en arrière", "term.navigateBack": "Naviguer en arrière",
"term.navigateForward": "Naviguer en avant", "term.navigateForward": "Naviguer en avant",
"term.play": "Lecture", "term.play": "Lecture",
"term.playpause": "Lecture/Pause",
"term.pause": "Pause", "term.pause": "Pause",
"term.stop": "Stop",
"term.previous": "Précédent", "term.previous": "Précédent",
"term.next": "Suivant", "term.next": "Suivant",
"term.shuffle": "Aléatoire", "term.shuffle": "Aléatoire",
@ -130,6 +132,8 @@
"term.audioControls": "Contrôles du volume", "term.audioControls": "Contrôles du volume",
"term.clearAll": "Tout effacer", "term.clearAll": "Tout effacer",
"term.recentStations": "Stations récentes", "term.recentStations": "Stations récentes",
"term.personalStations": "Stations personnelles",
"term.amLive": "Apple Music Live",
"term.language": "Langue", "term.language": "Langue",
"term.funLanguages": "Amusant", "term.funLanguages": "Amusant",
"term.noLyrics": "Chargement... / Paroles non trouvé./ Instrumental.", "term.noLyrics": "Chargement... / Paroles non trouvé./ Instrumental.",
@ -150,6 +154,7 @@
}, },
"term.videos": "Vidéos", "term.videos": "Vidéos",
"term.menu": "Menu", "term.menu": "Menu",
"term.themeManaged": "Géré par un thème",
"term.check": "Vérifier", "term.check": "Vérifier",
"term.aboutArtist": "À propos de {{artistName}}", "term.aboutArtist": "À propos de {{artistName}}",
"term.topResult": "Meilleurs résultats", "term.topResult": "Meilleurs résultats",
@ -174,7 +179,7 @@
"term.version": "Version", "term.version": "Version",
"term.noVideos": "Aucune vidéo trouvée.", "term.noVideos": "Aucune vidéo trouvée.",
"term.plugin": "Plugin", "term.plugin": "Plugin",
"term.pluginMenu": "Plug-in Menu", "term.pluginMenu": "Menu des plugins",
"term.pluginMenu.none": "Aucun plugin interactif", "term.pluginMenu.none": "Aucun plugin interactif",
"term.replay": "Replay", "term.replay": "Replay",
"term.uniqueAlbums": "Albums uniques", "term.uniqueAlbums": "Albums uniques",
@ -189,6 +194,16 @@
"term.confirmLogout": "Êtes-vous sûr de vouloir vous déconnecter ?", "term.confirmLogout": "Êtes-vous sûr de vouloir vous déconnecter ?",
"term.creditDesignedBy": "Conçu par ${authorUsername}", "term.creditDesignedBy": "Conçu par ${authorUsername}",
"term.discNumber": "Disque ${discNumber}", "term.discNumber": "Disque ${discNumber}",
"term.reload": "Recharger Cider ?",
"term.toggleprivate": "Activer/désactiver la session privée",
"term.webremote": "Télécommande Web",
"term.cast": "Diffuser",
"term.cast2": "Diffuser sur des appareils",
"term.quit": "Quitter",
"term.zoomin": "Zoom avant",
"term.zoomout": "Zoom Out",
"term.zoomreset": "Zoom arrière",
"term.fullscreen": "Plein écran",
"home.title": "Accueil", "home.title": "Accueil",
"home.recentlyPlayed": "Joué récemment", "home.recentlyPlayed": "Joué récemment",
"home.recentlyAdded": "Ajouté récemment", "home.recentlyAdded": "Ajouté récemment",
@ -209,9 +224,16 @@
"podcast.episodes": "Épisodes", "podcast.episodes": "Épisodes",
"podcast.playEpisode": "Lire l'épisode", "podcast.playEpisode": "Lire l'épisode",
"podcast.website": "Site du podcast", "podcast.website": "Site du podcast",
"action.hideLibrary": "Cacher la bibliothèque",
"action.showLibrary": "Afficher la bibliothèque",
"action.cut": "Couper",
"action.paste": "Coller",
"action.selectAll": "Tout sélectionner",
"action.delete": "Supprimer",
"action.edit": "Modifier", "action.edit": "Modifier",
"action.done": "Terminé", "action.done": "Terminé",
"action.editTracklist": "Edit Tracklist", "action.submit": "Soumettre",
"action.editTracklist": "Modifier la liste de morceaux",
"action.addToLibrary": "Ajouter à la bibliothèque", "action.addToLibrary": "Ajouter à la bibliothèque",
"action.addToLibrary.success": "Ajouté à la bibliothèque", "action.addToLibrary.success": "Ajouté à la bibliothèque",
"action.addToLibrary.error": "Erreur lors de l'ajout à la bibliothèque", "action.addToLibrary.error": "Erreur lors de l'ajout à la bibliothèque",
@ -275,45 +297,26 @@
"action.createNew": "Créer un nouveau...", "action.createNew": "Créer un nouveau...",
"action.openArtworkInBrowser": "Ouvrir la pochette d'album dans le navigateur", "action.openArtworkInBrowser": "Ouvrir la pochette d'album dans le navigateur",
"action.scrollToTop": "Défiler vers le haut", "action.scrollToTop": "Défiler vers le haut",
"menubar.options.about": "À propos",
"menubar.options.settings": "Paramètres",
"menubar.options.quit": "Quitter Cider",
"menubar.options.view": "Afficher ", "menubar.options.view": "Afficher ",
"menubar.options.reload": "Recharger", "menubar.options.reload": "Recharger",
"menubar.options.forcereload": "Rechargement forcé", "menubar.options.forcereload": "Rechargement forcé",
"menubar.options.toggledevtools": "Activer les outils de développement", "menubar.options.toggledevtools": "Activer les outils de développement",
"menubar.options.window": "Fenêtre", "menubar.options.window": "Fenêtre",
"menubar.options.minimize": "Minimiser", "menubar.options.minimize": "Minimiser",
"menubar.options.toggleprivate": "Activer la session privée",
"menubar.options.webremote": "Télécommande Web",
"menubar.options.audio": "Paramètres audio",
"menubar.options.plugins": "Menu des plugins", "menubar.options.plugins": "Menu des plugins",
"menubar.options.controls": "Contrôles", "menubar.options.controls": "Contrôles",
"menubar.options.next": "Suivant",
"menubar.options.playpause": "Lecture/Pause",
"menubar.options.previous": "Précédent",
"menubar.options.volumeup": "Augmenter le volume", "menubar.options.volumeup": "Augmenter le volume",
"menubar.options.volumedown": "Réduire le volume", "menubar.options.volumedown": "Réduire le volume",
"menubar.options.browse": "Parcourir",
"menubar.options.artists": "Artistes",
"menubar.options.search": "Search",
"menubar.options.albums": "Albums",
"menubar.options.cast": "Diffuser sur des appareils",
"menubar.options.account": "Compte", "menubar.options.account": "Compte",
"menubar.options.accountsettings": "Paramètres du compte",
"menubar.options.signout": "Se déconnecter", "menubar.options.signout": "Se déconnecter",
"menubar.options.support": "Support", "menubar.options.support": "Support",
"menubar.options.discord": "Discord",
"menubar.options.github": "Documentation GitHub",
"menubar.options.report": "Remonter un(e)...", "menubar.options.report": "Remonter un(e)...",
"menubar.options.bug": "Bug", "menubar.options.bug": "Bug",
"menubar.options.feature": "Demande de fonctionnalité", "menubar.options.feature": "Demande de fonctionnalité",
"menubar.options.trans": "Erreur/Demande de traduction", "menubar.options.trans": "Erreur/Demande de traduction",
"menubar.options.license": "Voir la license", "menubar.options.license": "Voir la license",
"menubar.options.conf": "Ouvrir le fichier de configuration dans l'éditeur", "menubar.options.conf": "Ouvrir le fichier de configuration dans l'éditeur",
"menubar.options.listennow": "Écoutez maintenant", "menubar.options.zoom": "Zoom",
"menubar.options.recentlyAdded": "Ajouté récemment",
"menubar.options.songs": "Musiques",
"settings.header.general": "Général", "settings.header.general": "Général",
"settings.header.general.description": "Ajuster les paramètres généraux de Cider.", "settings.header.general.description": "Ajuster les paramètres généraux de Cider.",
"settings.option.general.language": "Langue", "settings.option.general.language": "Langue",
@ -333,11 +336,15 @@
"settings.option.general.customizeSidebar": "Personnaliser les éléments de la barre latérale", "settings.option.general.customizeSidebar": "Personnaliser les éléments de la barre latérale",
"settings.option.general.customizeSidebar.customize": "Personnalisez", "settings.option.general.customizeSidebar.customize": "Personnalisez",
"settings.option.general.keybindings": "Raccourcis clavier", "settings.option.general.keybindings": "Raccourcis clavier",
"settings.option.general.keybindings.library": "Bibliothèque",
"settings.option.general.keybindings.session": "Session",
"settings.option.general.keybindings.control": "Contrôles",
"settings.option.general.keybindings.interface": "Interface",
"settings.option.general.keybindings.advanced": "Avancé",
"settings.option.general.keybindings.pressCombination": "Appuyez sur une combinaison de deux touches pour mettre à jour la combinaison de touches.", "settings.option.general.keybindings.pressCombination": "Appuyez sur une combinaison de deux touches pour mettre à jour la combinaison de touches.",
"settings.option.general.keybindings.pressEscape": "Appuyez sur la touche Échap pour revenir en arrière.", "settings.option.general.keybindings.pressEscape": "Appuyez sur la touche Échap pour revenir en arrière.",
"settings.notyf.general.keybindings.update.success": "Le raccourci clavier a bien été mis à jour", "settings.notyf.general.keybindings.update.success": "Le raccourci clavier a bien été mis à jour",
"settings.prompt.general.keybindings.update.success": "Le raccourci clavier a bien été mis à jour. Appuyez sur OK pour relancer Cider", "settings.prompt.general.keybindings.update.success": "Le raccourci clavier a bien été mis à jour. Appuyez sur OK pour relancer Cider",
"settings.option.general.keybindings.open": "Ouvrir",
"settings.option.general.themeUpdateNotification": "Vérifier automatiquement les mises à jour des thèmes", "settings.option.general.themeUpdateNotification": "Vérifier automatiquement les mises à jour des thèmes",
"settings.option.general.showLovedTracksInline": "Afficher les pistes aimées en ligne", "settings.option.general.showLovedTracksInline": "Afficher les pistes aimées en ligne",
"settings.description.search": "Rechercher", "settings.description.search": "Rechercher",
@ -360,6 +367,7 @@
"settings.header.audio": "Audio", "settings.header.audio": "Audio",
"settings.header.audio.description": "Ajuster les paramètres audio de Cider.", "settings.header.audio.description": "Ajuster les paramètres audio de Cider.",
"settings.option.audio.volumeStep": "Étape du volume", "settings.option.audio.volumeStep": "Étape du volume",
"settings.option.audio.advanced": "Contrôle avancé du volume",
"settings.option.audio.maxVolume": "Volume maximum", "settings.option.audio.maxVolume": "Volume maximum",
"settings.option.audio.changePlaybackRate": "Changer la vitesse de lecture", "settings.option.audio.changePlaybackRate": "Changer la vitesse de lecture",
"settings.option.audio.playbackRate": "Vitesse de lecture", "settings.option.audio.playbackRate": "Vitesse de lecture",
@ -394,12 +402,21 @@
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.description": "Changes the mode of operation of the Atmosphere Realizer module.", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.description": "Changes the mode of operation of the Atmosphere Realizer module.",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.NATURAL_STANDARD": "Naturelle (Standard)", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.NATURAL_STANDARD": "Naturelle (Standard)",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.NATURAL_PLUS": "Naturelle (Plus)", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.NATURAL_PLUS": "Naturelle (Plus)",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E68_1": "Fromage au sel de roche et thé mousseux",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E68_2": "Thé au lait Uji Matcha",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E168_1": "Macchiato au jasmin",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z3600": "Thé au lait Hokkaido",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500A": "Gâteau au clair de lune",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.BSCBM": "Lait crème brûlée au sucre brun",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.CUDDLE": "Chaleur des câlins",
"settings.option.audio.enableAdvancedFunctionality.ciderPPE": "Moteur de traitement psycho-acoustique Cider", "settings.option.audio.enableAdvancedFunctionality.ciderPPE": "Moteur de traitement psycho-acoustique Cider",
"settings.option.audio.enableAdvancedFunctionality.ciderPPE.description": "Améliore la qualité audio perçue de l'audio AAC 256 kbps en utilisant un algorithme en temps réel qui tire parti à la fois des modèles psychoacoustiques de l'audition humaine et des caractéristiques de codage AAC.", "settings.option.audio.enableAdvancedFunctionality.ciderPPE.description": "Améliore la qualité audio perçue de l'audio AAC 256 kbps en utilisant un algorithme en temps réel qui tire parti à la fois des modèles psychoacoustiques de l'audition humaine et des caractéristiques de codage AAC.",
"settings.warn.audio.enableAdvancedFunctionality.ciderPPE.compatibility": "Le moteur de traitement psycho-acoustique n'est pas compatible avec la spatialisation. Veuillez désactiver la spatialisation pour continuer.", "settings.warn.audio.enableAdvancedFunctionality.ciderPPE.compatibility": "Le moteur de traitement psycho-acoustique n'est pas compatible avec la spatialisation. Veuillez désactiver la spatialisation pour continuer.",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength": "Force du moteur de traitement psycho-acoustique", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength": "Force du moteur de traitement psycho-acoustique",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.description": "Modifie l'intensité du traitement effectué sur l'audio. (Un traitement agressif peut donner des résultats indésirables).", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.description": "Modifie l'intensité du traitement effectué sur l'audio. (Un traitement agressif peut donner des résultats indésirables).",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.standard": "Standard", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.standard": "Standard",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.adaptive": "Adaptive",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.legacy": "Ancien",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.aggressive": "Agressive", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.aggressive": "Agressive",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Normalisation du son", "settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Normalisation du son",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normalise le volume maximal des pistes individuelles pour créer une expérience d'écoute plus uniforme.", "settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normalise le volume maximal des pistes individuelles pour créer une expérience d'écoute plus uniforme.",
@ -413,6 +430,9 @@
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.separation": "Séparation", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.separation": "Séparation",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.minimal": "Minimale", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.minimal": "Minimale",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.audiophile": "Audiophile", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.audiophile": "Audiophile",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.diffused": "Diffusé",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.bplk": "Encore",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.hw2k": "Encore élargi",
"settings.warn.audio.enableAdvancedFunctionality.audioSpatialization.compatibility": "La spatialisation n'est pas compatible avec le moteur de traitement psycho-acoustique. Veuillez le désactiver pour continuer.", "settings.warn.audio.enableAdvancedFunctionality.audioSpatialization.compatibility": "La spatialisation n'est pas compatible avec le moteur de traitement psycho-acoustique. Veuillez le désactiver pour continuer.",
"settings.option.audio.dbspl.display": "Affichage dB SPL", "settings.option.audio.dbspl.display": "Affichage dB SPL",
"settings.option.audio.dbspl.description": "(Utilisateurs avancés uniquement) Affichez dB SPL au lieu de dBFS sur le curseur de volume.", "settings.option.audio.dbspl.description": "(Utilisateurs avancés uniquement) Affichez dB SPL au lieu de dBFS sur le curseur de volume.",
@ -494,11 +514,19 @@
"settings.option.connectivity.discordRPC.hideTimestamp": "Cacher le temps restant sur le Discord Rich Presence", "settings.option.connectivity.discordRPC.hideTimestamp": "Cacher le temps restant sur le Discord Rich Presence",
"settings.option.connectivity.discordRPC.detailsFormat": "Format des détails", "settings.option.connectivity.discordRPC.detailsFormat": "Format des détails",
"settings.option.connectivity.discordRPC.stateFormat": "Format de l'état", "settings.option.connectivity.discordRPC.stateFormat": "Format de l'état",
"settings.option.connectivity.discordRPC.reload": "Recharger DiscordRPC",
"settings.option.connectivity.discordRPC.reconnectedToUser": "DiscordRPC Reconnecté à l'utilisateur: {{user}} ({{userid}})",
"settings.option.connectivity.lastfmScrobble": "Scrobble LastFM", "settings.option.connectivity.lastfmScrobble": "Scrobble LastFM",
"settings.option.connectivity.lastfmScrobble.delay": "Délai de Scrobble LastFM (%)", "settings.option.connectivity.lastfmScrobble.delay": "Délai de Scrobble LastFM (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Activer la lecture en cours sur LastFM", "settings.option.connectivity.lastfmScrobble.nowPlaying": "Activer la lecture en cours sur LastFM",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Supprimer les artistes en vedette du titre de la chanson (LastFM)", "settings.option.connectivity.lastfmScrobble.removeFeatured": "Supprimer les artistes en vedette du titre de la chanson (LastFM)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filtrer les titres en boucle (LastFM)", "settings.option.connectivity.lastfmScrobble.filterLoop": "Filtrer les titres en boucle (LastFM)",
"settings.option.connectivity.lastfmScrobble.filterLoop.description": "Empêcher les pistes en boucle d'être scrobbulées ou affichées dans la liste En cours de lecture sur Last.fm.",
"settings.option.connectivity.lastfmScrobble.filterTypes": "Types de médias filtrés (Last.fm)",
"settings.option.connectivity.lastfmScrobble.manualToken": "Entrer manuellement le jeton Last.fm",
"settings.notyf.connectivity.lastfmScrobble.connectError": "Last.fm Connection échouée",
"settings.notyf.connectivity.lastfmScrobble.connectSuccess": "Last.fm Connection réussie",
"settings.notyf.connectivity.lastfmScrobble.connecting": "Connexion à Last.fm...",
"settings.header.debug": "Débogage", "settings.header.debug": "Débogage",
"settings.option.debug.copy_log": "Copier les logs dans le presse-papiers", "settings.option.debug.copy_log": "Copier les logs dans le presse-papiers",
"settings.option.debug.openAppData": "Ouvrir le dossier de Cider", "settings.option.debug.openAppData": "Ouvrir le dossier de Cider",
@ -551,5 +579,44 @@
"share.platform.email": "Email", "share.platform.email": "Email",
"share.platform.songLink": "Copier avec song.link", "share.platform.songLink": "Copier avec song.link",
"share.platform.clipboard": "Copier le lien", "share.platform.clipboard": "Copier le lien",
"about.thanks": "Un grand merci à l'équipe de la Cider Collective et à tous nos contributeurs." "about.thanks": "Un grand merci à l'équipe de la Cider Collective et à tous nos contributeurs.",
"oobe.yes": "Oui",
"oobe.no": "Non",
"oobe.next": "Suivant",
"oobe.previous": "Précédent",
"oobe.done": "Terminé",
"oobe.amupsell.title": "Avant de commencer",
"oobe.amupsell.text": "Cider nécessite un abonnement Apple Music actif et payant.\nCider ne fonctionne pas avec l'offre Apple Music Voice ou certains abonnements d'essai promotionnels. Si vous avez déjà un abonnement Apple Music qualifié, cliquez sur Suivant pour continuer.",
"oobe.amupsell.subscribeBtn": "S'abonner à Apple Music",
"oobe.amupsell.explainBtn": "Expliquer",
"oobe.amupsell.subscribeUrl": "https://apple.co/3MdqJVQ",
"oobe.amupsell.amWebUrl": "https://beta.music.apple.com/",
"oobe.amupsell.promoExplained": "Certains abonnements d'essai Apple Music promotionnels et non américains n'ont pas accès aux API du lecteur web Apple Music nécessaires au fonctionnement de Cider. Pour vérifier si votre version d'essai active fonctionnera avec Cider, rendez-vous à l'adresse suivante <a href='{{ amWebUrl }}'>{{ amWebUrl }}</a> connectez-vous et essayez de jouer de la musique. Si cela fonctionne, tant mieux ! Vous êtes prêt à utiliser Cider, mais si ce n'est pas le cas, abonnez-vous à Apple Music ici : <a href='{{ subscribeUrl }}'>{{ subscribeUrl }}</a>",
"oobe.intro.title": "Bienvenue sur Cider",
"oobe.intro.subtitle": "",
"oobe.intro.text": "Nous allons mettre en place quelques éléments pour que vous puissiez utiliser Cider comme vous le souhaitez. Vous pourrez toujours modifier ces paramètres plus tard.",
"oobe.general.title": "Général",
"oobe.general.subtitle": "",
"oobe.general.text": "",
"oobe.audio.title": "Audio",
"oobe.audio.subtitle": "",
"oobe.audio.text": "Cider dispose d'une pile audio personnalisée et conçue pour offrir une expérience audio riche et de haute qualité.\nIl comprend le moteur de traitement psycho-acoustique Cider, le réalisateur d'atmosphère et la spatialisation de l'audio.\nPour activer cette fonctionnalité, la fonction \"Fonctionnalité audio avancée\" doit être activée.\nL'activation de la fonctionnalité audio avancée vous donnera accès à ces améliorations dans les laboratoires audio de Cider, qui se trouvent dans les paramètres de l'application.",
"oobe.audio.advancedFunctionality": "",
"oobe.visual.title": "Visuel",
"oobe.visual.subtitle": "",
"oobe.visual.text": "",
"oobe.visual.layout.text": "Cider présente deux agencements de fenêtres différents.\nMaverick est une mise en page semblable à celle d'iTunes, avec le lecteur en haut de la fenêtre.\nMojave est une nouvelle agencement créé par la Cider Collective.\n\nVous pouvez modifier la mise en page à tout moment dans les paramètres.",
"oobe.visual.suggestingThemes": "Les thèmes sont un excellent moyen de personnaliser votre expérience. En voici quelques-uns que nous vous suggérons : ",
"oobe.visual.suggestingThemes.subtext": "(Ces thèmes seront téléchargés à partir de GitHub)",
"oobe.visual.suggestingThemes.default": "Cider",
"oobe.visual.suggestingThemes.default.text": "Le thème classique de Cider.",
"oobe.visual.suggestingThemes.dark": "Sombre",
"oobe.visual.suggestingThemes.dark.text": "L'obscurité.",
"oobe.visual.suggestingThemes.community1": "Groovy",
"oobe.visual.suggestingThemes.community1.text": "Un thème influencé par WinUI",
"oobe.visual.suggestingThemes.community2": "iTheme",
"oobe.visual.suggestingThemes.community2.text": "La disposition classique des gros fruits.",
"oobe.visual.suggestingThemes.community3": "Dracula",
"oobe.visual.suggestingThemes.community3.text": "L'emblématique combinaison de couleurs de Dracula.",
"oobe.amsignin.title": ""
} }

View file

@ -101,7 +101,7 @@
"term.recentStations": "Recent Stations", "term.recentStations": "Recent Stations",
"term.language": "Language", "term.language": "Language",
"term.funLanguages": "Fun", "term.funLanguages": "Fun",
"term.noLyrics": "Loading... / Lyrics not found./ Instrumental.", "term.noLyrics": "Instrumental Track / No Lyrics.",
"term.copyright": "Copyright", "term.copyright": "Copyright",
"term.rightsReserved": "All Rights Reserved.", "term.rightsReserved": "All Rights Reserved.",
"term.sponsor": "Sponsor this project", "term.sponsor": "Sponsor this project",

View file

@ -10,6 +10,7 @@
"notification.updatingLibrarySongs": "Memperbarui Pustaka lagu...", "notification.updatingLibrarySongs": "Memperbarui Pustaka lagu...",
"notification.updatingLibraryAlbums": "Memperbarui Pustaka album...", "notification.updatingLibraryAlbums": "Memperbarui Pustaka album...",
"notification.updatingLibraryArtists": "Memperbarui Pustaka artis...", "notification.updatingLibraryArtists": "Memperbarui Pustaka artis...",
"term.variables": "Variabel",
"term.appleInc": "Apple Inc.", "term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music", "term.appleMusic": "Apple Music",
"term.applePodcasts": "Apple Podcasts", "term.applePodcasts": "Apple Podcasts",
@ -20,9 +21,12 @@
"term.accountSettings": "Pengaturan Akun", "term.accountSettings": "Pengaturan Akun",
"term.logout": "Keluar", "term.logout": "Keluar",
"term.login": "Masuk", "term.login": "Masuk",
"term.quickNav": "Navigasi Cepat",
"term.about": "Tentang", "term.about": "Tentang",
"term.privateSession": "Sesi Pribadi", "term.privateSession": "Sesi Pribadi",
"term.disablePrivateSession": "Matikan Mode Pribadi",
"term.queue": "Antrian", "term.queue": "Antrian",
"term.autoplay": "Pemutar Otomatis",
"term.lyrics": "Lirik", "term.lyrics": "Lirik",
"term.miniplayer": "Pemutar Kecil", "term.miniplayer": "Pemutar Kecil",
"term.history": "Riwayat", "term.history": "Riwayat",
@ -37,18 +41,28 @@
"term.artists": "Artis", "term.artists": "Artis",
"term.podcasts": "Podcasts", "term.podcasts": "Podcasts",
"term.playlists": "Playlist", "term.playlists": "Playlist",
"term.charts": "Tangga Lagu",
"term.playlist": "Playlist", "term.playlist": "Playlist",
"term.newPlaylist": "Playlist Baru", "term.newPlaylist": "Playlist Baru",
"term.newPlaylistFolder": "Folder Playlist Baru", "term.newPlaylistFolder": "Folder Playlist Baru",
"term.createNewPlaylist": "Buat Playlist Baru", "term.createNewPlaylist": "Buat Playlist Baru",
"term.createNewPlaylistFolder": "Buat Folder Playlist Baru", "term.createNewPlaylistFolder": "Buat Folder Playlist Baru",
"term.deletePlaylist": "Yakin ingin menghapus playlist ini?", "term.deletePlaylist": "Yakin ingin menghapus playlist ini?",
"term.navigateBack": "Navigasi kembali",
"term.navigateForward": "Navigasi kedepan",
"term.play": "Mainkan", "term.play": "Mainkan",
"term.playpause": "Mainkan/Jeda",
"term.pause": "Jeda", "term.pause": "Jeda",
"term.stop": "Berhenti",
"term.previous": "Sebelumnya", "term.previous": "Sebelumnya",
"term.next": "Selanjutnya", "term.next": "Selanjutnya",
"term.shuffle": "Acak", "term.shuffle": "Acak",
"term.enableShuffle": "Nyalakan pemutaran acak",
"term.disableShuffle": "Matikan pemutaran acak",
"term.repeat": "Ulangi", "term.repeat": "Ulangi",
"term.enableRepeatOne": "Aktifkan ulangi sekali",
"term.disableRepeatOne": "Matikan ulangi sekali",
"term.disableRepeat": "Matikan ulangi",
"term.volume": "Volume", "term.volume": "Volume",
"term.mute": "Bisu", "term.mute": "Bisu",
"term.unmute": "Bunyikan", "term.unmute": "Bunyikan",
@ -70,6 +84,7 @@
"term.viewAs": "Lihat Sebagai", "term.viewAs": "Lihat Sebagai",
"term.viewAs.coverArt": "Cover Art", "term.viewAs.coverArt": "Cover Art",
"term.viewAs.list": "Daftar", "term.viewAs.list": "Daftar",
"term.dynamic": "Dinamis",
"term.size": "Ukuran", "term.size": "Ukuran",
"term.size.normal": "Normal", "term.size.normal": "Normal",
"term.size.compact": "Kompak", "term.size.compact": "Kompak",
@ -117,6 +132,9 @@
"term.audioControls": "Kontrol Volume", "term.audioControls": "Kontrol Volume",
"term.clearAll": "Bersihkan Semua", "term.clearAll": "Bersihkan Semua",
"term.recentStations": "Stasiun Terbaru", "term.recentStations": "Stasiun Terbaru",
"term.personalStations": "Stasiun Pribadi",
"term.amLive": "Apple Music Live",
"term.live": "LIVE",
"term.language": "Bahasa", "term.language": "Bahasa",
"term.funLanguages": "Senang-senang", "term.funLanguages": "Senang-senang",
"term.noLyrics": "Memuat... / Lirik tidak ditermukan./ Instrumental.", "term.noLyrics": "Memuat... / Lirik tidak ditermukan./ Instrumental.",
@ -137,6 +155,7 @@
}, },
"term.videos": "Video", "term.videos": "Video",
"term.menu": "Menu", "term.menu": "Menu",
"term.themeManaged": "Diatur oleh tema",
"term.check": "Cek", "term.check": "Cek",
"term.aboutArtist": "Tentang {{artistName}}", "term.aboutArtist": "Tentang {{artistName}}",
"term.topResult": "Hasil Teratas", "term.topResult": "Hasil Teratas",
@ -175,6 +194,19 @@
"term.topGenres": "Genre Teratas", "term.topGenres": "Genre Teratas",
"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.reload" : "Muat ulang Cider?",
"term.toggleprivate" : "Nyalakan Sesi Pribadi",
"term.webremote" : "Remot Web",
"term.cast" : "Transmisi",
"term.cast2" : "Transmisikan ke Perangkat",
"term.quit" : "Keluar",
"term.zoomin" : "Perbesar",
"term.zoomout" : "Perkecil",
"term.zoomreset" : "Atur Ulang",
"term.fullscreen" : "Layar Penuh",
"home.syncFavorites": "Sinkronkan Favorit",
"home.syncFavorites.gettingArtists": "Mendapatkan artis favorit",
"home.title": "Beranda", "home.title": "Beranda",
"home.recentlyPlayed": "Baru Dimainkan", "home.recentlyPlayed": "Baru Dimainkan",
"home.recentlyAdded": "Baru Ditambahkan", "home.recentlyAdded": "Baru Ditambahkan",
@ -187,14 +219,25 @@
"error.connectionError": "Terjadi masalah saat menyambungkan ke Apple Music.", "error.connectionError": "Terjadi masalah saat menyambungkan ke Apple Music.",
"error.noResults": "Tidak ada hasil.", "error.noResults": "Tidak ada hasil.",
"error.noResults.description": "Coba pencarian baru.", "error.noResults.description": "Coba pencarian baru.",
"podcast.followOnCider": "Ikuti Di Cider", "podcast.followOnCider": "Ikuti di Cider",
"podcast.followedOnCider": "Mengikuti Di Cider", "podcast.followedOnCider": "Mengikuti di Cider",
"podcast.subscribeOnItunes": "Langganan Di iTunes", "podcast.subscribeOnItunes": "Langganan di iTunes",
"podcast.subscribedOnItunes": "Berlangganan Di iTunes", "podcast.subscribedOnItunes": "Telah Berlangganan di iTunes",
"podcast.itunesStore": "iTunes Store", "podcast.itunesStore": "iTunes Store",
"podcast.episodes": "Episode", "podcast.episodes": "Episode",
"podcast.playEpisode": "Mainkan Episode", "podcast.playEpisode": "Mainkan Episode",
"podcast.website": "Website Podcast", "podcast.website": "Website Podcast",
"action.favorite": "Favorit",
"action.removeFavorite": "Hapus Favorit",
"action.hideLibrary": "Sembunyikan Pustaka",
"action.showLibrary": "Tampilkan Pustaka",
"action.cut": "Cut",
"action.paste": "Paste",
"action.selectAll": "Pilih Semua",
"action.delete": "Hapus",
"action.edit": "Ubah",
"action.done": "Selesai",
"action.editTracklist": "Edit Daftar Lagu",
"action.addToLibrary": "Tambahkan ke Pustaka", "action.addToLibrary": "Tambahkan ke Pustaka",
"action.addToLibrary.success": "Ditambahkan ke Pustaka", "action.addToLibrary.success": "Ditambahkan ke Pustaka",
"action.addToLibrary.error": "Terjadi Kesalahan Saat Menambahkan Pustaka", "action.addToLibrary.error": "Terjadi Kesalahan Saat Menambahkan Pustaka",
@ -240,12 +283,45 @@
"action.tray.minimize": "Sembunyikan ke Tray", "action.tray.minimize": "Sembunyikan ke Tray",
"action.tray.quit": "Keluar", "action.tray.quit": "Keluar",
"action.update": "Perbarui", "action.update": "Perbarui",
"action.tray.listento": "Dengarkan:",
"action.install": "Pasang", "action.install": "Pasang",
"action.copy": "Salin", "action.copy": "Salin",
"action.newpreset": "Preset Baru...", "action.newpreset": "Preset Baru...",
"action.deletepreset": "Hapus Preset", "action.deletepreset": "Hapus Preset",
"action.open": "Buka", "action.open": "Buka",
"action.close": "Close",
"action.relaunch.confirm": "Apakah Anda ingin memulai ulang Cider?", "action.relaunch.confirm": "Apakah Anda ingin memulai ulang Cider?",
"action.cast.chromecast": "Chromecast",
"action.cast.todevices": "Transmisi ke Perangkat",
"action.cast.stop": "Stop transmisi ke semua perangkat",
"action.cast.airplay": "AirPlay",
"action.cast.airplay.underdevelopment": "AirPlay masih dalam tahap pengembangan",
"action.cast.scan": "Pindai",
"action.cast.scanning": "Pindai...",
"action.createNew": "Buat Baru...",
"action.openArtworkInBrowser": "Buka artwork di browser",
"action.scrollToTop": "Gulir ke atas",
"action.refresh": "Muat ulang",
"menubar.options.view": "Tampilan",
"menubar.options.reload": "Muat Ulang",
"menubar.options.forcereload": "Paksa Muat Ulang",
"menubar.options.toggledevtools": "Aktfikan Alat Developer",
"menubar.options.window": "Jendela",
"menubar.options.minimize": "Perkecil",
"menubar.options.plugins": "Menu Plu-gins",
"menubar.options.controls": "Kontrol",
"menubar.options.volumeup": "Keraskan Volume",
"menubar.options.volumedown": "Kecilkan Volume",
"menubar.options.account": "Akun",
"menubar.options.signout": "Keluar",
"menubar.options.support": "Bantuan",
"menubar.options.report": "Laporkan...",
"menubar.options.bug": "Bug",
"menubar.options.feature": "Permintaan Fitur",
"menubar.options.trans": "Laporkan/Minta Terjemahan",
"menubar.options.license": "Lihat Lisensi",
"menubar.options.conf": "Bukan File Konfigurasi pada Editor",
"menubar.options.zoom": "Zoom",
"settings.header.general": "Umum", "settings.header.general": "Umum",
"settings.header.general.description": "Sesuaikan pengaturan umum untuk Cider.", "settings.header.general.description": "Sesuaikan pengaturan umum untuk Cider.",
"settings.option.general.language": "Bahasa", "settings.option.general.language": "Bahasa",
@ -254,17 +330,53 @@
"settings.option.general.resumebehavior.locally": "Lokal", "settings.option.general.resumebehavior.locally": "Lokal",
"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 menambahkan lagu terakhir dari keseluruhan riwayat Apple Music Anda ke dalam antrian di seluruh perangkat.", "settings.option.general.resumebehavior.history.description": "Cider akan melanjutkan lagu terakhir dari riwayat Apple Music di seluruh perangkat Anda.",
"settings.option.general.resumetabs" : "Buka Tab ketika Diluncurkan",
"settings.option.general.resumetabs.description" : "Anda dapat memilih tab apa yang akan dibuka ketika Anda membuka Cider.",
"settings.option.general.resumetabs.dynamic" : "Dinamis",
"settings.option.general.resumetabs.dynamic.description" : "Cider akan membuka tab yang terakhir digunakan",
"settings.option.general.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",
"settings.option.general.customizeSidebar": "Sesuaikan Item Sidebar",
"settings.option.general.customizeSidebar.customize": "Sesuaikan",
"settings.option.general.keybindings": "Kombinasi Keyboard",
"settings.option.general.keybindings.library": "Pustaka",
"settings.option.general.keybindings.session": "Sesi",
"settings.option.general.keybindings.control": "Kontrol",
"settings.option.general.keybindings.interface": "Tampilan",
"settings.option.general.keybindings.advanced": "Lebih Lanjut",
"settings.option.general.keybindings.pressCombination": "Tekan kombinasi dua tombol untuk memperbarui",
"settings.option.general.keybindings.pressEscape": "Tekan ESC untuk kembali",
"settings.notyf.general.keybindings.update.success": "Kombinasi Keyboard telah diperbarui",
"settings.prompt.general.keybindings.update.success": "Kombinasi keyboard telah diperbarui",
"settings.option.general.themeUpdateNotification": "Perbarui tema secara otomatis",
"settings.option.general.showLovedTracksInline": "Tampilkan lagu yang di-love sejajar",
"settings.description.search": "Cari",
"settings.description.albums": "Pustaka Album",
"settings.description.artists": "Pustaka Artis",
"settings.description.browse": "Jelajahi",
"settings.description.private": "Nyalakan Sesi Pribadi",
"settings.description.remote": "Remote Web",
"settings.description.audio": "Peraturan Audio",
"settings.description.plugins": "Menu Plugins",
"settings.description.cast": "Transmisikan ke Perangkat",
"settings.description.settings": "Pengaturan",
"settings.description.developer": "Alat Developer",
"settings.description.listnow": "Dengarkan Sekarang",
"settings.description.recentAdd": "Baru Ditambahkan",
"settings.description.songs": "Lagu",
"settings.notyf.updateCider.update-not-available": "Tidak ada pembaruan tersedia", "settings.notyf.updateCider.update-not-available": "Tidak ada pembaruan tersedia",
"settings.notyf.updateCider.update-downloaded": "Pembaruan sudah didownload, mulai ulang untuk menginstall pembaruan", "settings.notyf.updateCider.update-downloaded": "Pembaruan sudah didownload, mulai ulang untuk menginstall pembaruan",
"settings.notyf.updateCider.update-timeout": "Waktu pembaruan habis", "settings.notyf.updateCider.update-timeout": "Waktu pembaruan habis",
"settings.header.audio": "Audio", "settings.header.audio": "Audio",
"settings.header.audio.description": "Sesuaikan pengaturan audio untuk Cider.", "settings.header.audio.description": "Sesuaikan pengaturan audio untuk Cider.",
"settings.option.audio.volumeStep": "Jangkah Volume", "settings.option.audio.volumeStep": "Jangkah Volume",
"settings.option.audio.advanced": "Kontrol Volume Lanjut",
"settings.option.audio.maxVolume": "Volume Maksimal", "settings.option.audio.maxVolume": "Volume Maksimal",
"settings.option.audio.changePlaybackRate": "Ubah Tingkat Pemutaran",
"settings.option.audio.playbackRate": "Tingkat Pemutaran",
"settings.option.audio.playbackRate.change": "Ganti",
"settings.option.audio.quality": "Kualitas Audio", "settings.option.audio.quality": "Kualitas Audio",
"settings.header.audio.quality.hireslossless": "Hi-Res Lossless", "settings.header.audio.quality.hireslossless": "Hi-Res Lossless",
"settings.header.audio.quality.hireslossless.description": "sampai dari 24-bit/192 kHz", "settings.header.audio.quality.hireslossless.description": "sampai dari 24-bit/192 kHz",
@ -277,20 +389,41 @@
"settings.option.audio.seamlessTransition": "Transisi Audio Mulus", "settings.option.audio.seamlessTransition": "Transisi Audio Mulus",
"settings.option.audio.enableAdvancedFunctionality": "Aktifkan Fungsi Lanjutan", "settings.option.audio.enableAdvancedFunctionality": "Aktifkan Fungsi Lanjutan",
"settings.option.audio.enableAdvancedFunctionality.description": "Mengaktifkan fungsionalitas AudioContext memungkinkan fitur audio lanjutan seperti Normalisasi Audio, Equalizer dan Visualizer. Namun pada beberapa perangkat dapat menyebabkan tersendatnya audio.", "settings.option.audio.enableAdvancedFunctionality.description": "Mengaktifkan fungsionalitas AudioContext memungkinkan fitur audio lanjutan seperti Normalisasi Audio, Equalizer dan Visualizer. Namun pada beberapa perangkat dapat menyebabkan tersendatnya audio.",
"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.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.option.audio.enableAdvancedFunctionality.analogWarmth": "Analog Warmth", "settings.option.audio.enableAdvancedFunctionality.analogWarmth": "Analog Warmth",
"settings.option.audio.enableAdvancedFunctionality.analogWarmth.description": "Mensimulasikan kehangatan analog yang dimodelkan setelah Korg Nutube 6P1", "settings.option.audio.enableAdvancedFunctionality.analogWarmth.description": "Mensimulasikan kehangatan analog yang dimodelkan setelah Korg Nutube 6P1",
"settings.option.audio.enableAdvancedFunctionality.analogWarmthIntensity": "Analog Warmth intensity", "settings.option.audio.enableAdvancedFunctionality.analogWarmthIntensity": "Analog Warmth intensity",
"settings.option.audio.enableAdvancedFunctionality.analogWarmthIntensity.description": "Mengubah intensitas pemrosesan modul Analog Warmth.", "settings.option.audio.enableAdvancedFunctionality.analogWarmthIntensity.description": "Mengubah intensitas pemrosesan modul Analog Warmth.",
"settings.option.audio.enableAdvancedFunctionality.analogWarmthIntensity.smooth": "Halus", "settings.option.audio.enableAdvancedFunctionality.analogWarmthIntensity.smooth": "Halus",
"settings.option.audio.enableAdvancedFunctionality.analogWarmthIntensity.warm": "Hangat", "settings.option.audio.enableAdvancedFunctionality.analogWarmthIntensity.warm": "Hangat",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizer": "Cider Atmosphere Realizer™",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizer.description": "Membuat atmosfir musik yang berbeda setelah penyesuaian audio tingkat modern dan terbaru",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode": "Cider Atmosphere Realizer™ Mode",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.description": "Ubah mode operasi dari modul Atmosphere Realizer.",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.NATURAL_STANDARD": "Hōjicha Cheese Foam Tea",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.NATURAL_PLUS": "Genmaicha Tapioca Milk Tea",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E68_1": "Rock Salt Cheese Foam Tea",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E68_2": "Uji Matcha Milk Tea",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E168_1": "Jasmine Macchiato",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z3600": "Hokkaido Milk Tea",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500A": "Moonlight Softcake",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500B": "Clafoutis aux Cerises",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500C": "Uji Matcha Mochi",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.BSCBM": "Brown Sugar Creme Brûlée Milk",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.CUDDLE": "Cuddle Warmth",
"settings.option.audio.enableAdvancedFunctionality.ciderPPE": "Cider Adrenaline Processor™", "settings.option.audio.enableAdvancedFunctionality.ciderPPE": "Cider Adrenaline Processor™",
"settings.option.audio.enableAdvancedFunctionality.ciderPPE.description": "Meningkatkan kualitas audio AAC secara 'realtime' dengan algoritma yang memanfaatkan model psychoacoustic manusia dan karakteristik encoding AAC",
"settings.warn.audio.enableAdvancedFunctionality.ciderPPE.compatibility": "CAP tidak cocok dengan Spatialisasi. Matikan Spasialisasi untuk menggunakan.", "settings.warn.audio.enableAdvancedFunctionality.ciderPPE.compatibility": "CAP tidak cocok dengan Spatialisasi. Matikan Spasialisasi untuk menggunakan.",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength": "Kekuatan CAP", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength": "Kekuatan CAP",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.description": "Mengubah kekuatan pemrosesan yang dilakukan pada audio. (Agresif dapat menghasilkan hasil yang tidak diinginkan)", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.description": "Mengubah kekuatan pemrosesan yang dilakukan pada audio. (Agresif dapat menghasilkan hasil yang tidak diinginkan)",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.standard": "Standar", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.standard": "Standar",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.adaptive": "Adaptif",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.legacy": "Legacy",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.aggressive": "Agresif", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.aggressive": "Agresif",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Normalisasi Audio", "settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Normalisasi Audio",
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Menormalkan puncak volume untuk masing-masing lagu demi menciptakan pengalaman mendengarkan yang lebih seragam.", "settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Menormalkan puncak volume untuk masing-masing lagu demi menciptakan pengalaman mendengarkan yang lebih seragam.",
@ -300,14 +433,30 @@
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile": "Profil Spasialisasi Cider", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile": "Profil Spasialisasi Cider",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.description": "Mengubah Profil Tuning Spasialisasi. (Memerlukan Restart Aplikasi)", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.description": "Mengubah Profil Tuning Spasialisasi. (Memerlukan Restart Aplikasi)",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.standard": "Standar", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.standard": "Standar",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.soundstage": "Soundstage",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.separation": "Separation",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.minimal": "Minimal",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.audiophile": "Audiophile", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.audiophile": "Audiophile",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.diffused": "Diffused",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.bplk": "Encore",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.hw2k": "Expanded Encore",
"settings.warn.audio.enableAdvancedFunctionality.audioSpatialization.compatibility": "Spatialization tidak cocok dengan CAP. Matikan CAP untuk menggunakan.", "settings.warn.audio.enableAdvancedFunctionality.audioSpatialization.compatibility": "Spatialization tidak cocok dengan CAP. Matikan CAP untuk menggunakan.",
"settings.option.audio.dbspl.display": "dB SPL Display",
"settings.option.audio.dbspl.description": "(Untuk pengguna ahli) Tampilkan dB SPL daripada dBFS pada penggeser volume.",
"settings.option.audio.dbfs.calibration": "0 dBFS Calibration",
"settings.option.audio.dbfs.description": "Masukkan puncak Z-weighted dB SPL saat Cider berada pada 0 dBFS.",
"settings.header.visual": "Visual", "settings.header.visual": "Visual",
"settings.header.visual.description": "Sesuaikan pengaturan visual untuk Cider.", "settings.header.visual.description": "Sesuaikan pengaturan visual untuk Cider.",
"settings.option.visual.windowStyle": "Gaya Jendela",
"settings.option.visual.customAccentColor": "Warna Pilihan",
"settings.option.visual.accentColor": "Warna",
"settings.option.visual.purplePodcastPlaybackBar": "Bilah Pemutaran Ungu untuk Podcast",
"settings.option.visual.windowColor": "Warna Jendela",
"settings.option.visual.windowBackgroundStyle": "Gaya Latar Belakang Jendela", "settings.option.visual.windowBackgroundStyle": "Gaya Latar Belakang Jendela",
"settings.header.visual.windowBackgroundStyle.none": "Tidak Ada", "settings.header.visual.windowBackgroundStyle.none": "Tidak Ada",
"settings.header.visual.windowBackgroundStyle.artwork": "Artwork", "settings.header.visual.windowBackgroundStyle.artwork": "Artwork",
"settings.header.visual.windowBackgroundStyle.image": "Gambar", "settings.header.visual.windowBackgroundStyle.image": "Gambar",
"settings.header.visual.windowBackgroundStyle.color": "Warna-warna",
"settings.option.visual.animatedArtwork": "Artwork Bergerak", "settings.option.visual.animatedArtwork": "Artwork Bergerak",
"settings.header.visual.animatedArtwork.always": "Selalu", "settings.header.visual.animatedArtwork.always": "Selalu",
"settings.header.visual.animatedArtwork.limited": "Terbatas untuk halaman dan entri khusus", "settings.header.visual.animatedArtwork.limited": "Terbatas untuk halaman dan entri khusus",
@ -323,12 +472,21 @@
"settings.option.visual.hardwareAcceleration.description": "Membutuhkan dibuka ulang", "settings.option.visual.hardwareAcceleration.description": "Membutuhkan dibuka ulang",
"settings.header.visual.hardwareAcceleration.default": "Default", "settings.header.visual.hardwareAcceleration.default": "Default",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU", "settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
"settings.option.visual.uiscale": "Skala UI",
"settings.header.visual.theme": "Tema", "settings.header.visual.theme": "Tema",
"settings.option.visual.theme.github.download": "Pasang dari URL GitHub", "settings.option.visual.theme.github.download": "Pasang dari URL GitHub",
"settings.option.visual.theme.github.openfolder": "Buka Folder Tema",
"settings.option.visual.theme.github.explore": "Jelajahi Tema di GitHub", "settings.option.visual.theme.github.explore": "Jelajahi Tema di GitHub",
"settings.header.visual.theme.github.page": "Tema dari GitHub", "settings.header.visual.theme.github.page": "Tema dari GitHub",
"settings.option.visual.theme.github.install.confirm": "Apakah anda yakin untuk memasang {{ repo }}?", "settings.option.visual.theme.github.install.confirm": "Apakah anda yakin untuk memasang {{ repo }}?",
"settings.prompt.visual.theme.github.URL": "Masukan URL tema yang ingin Anda pasang", "settings.prompt.visual.theme.github.URL": "Masukan URL tema yang ingin Anda pasang",
"settings.prompt.visual.theme.uninstallTheme": "Apakah Anda yakin untuk untuk mencopot {{ theme }}?",
"settings.option.visual.theme.checkForUpdates": "Cek Pembaruan",
"settings.option.visual.theme.manageStyles": "Kelola Gaya",
"settings.option.visual.theme.uninstall": "Copot",
"settings.option.visual.theme.viewInfo": "Lihat Info",
"settings.option.visual.theme.github.available": "Tersedia",
"settings.option.visual.theme.github.applied": "Diterapkan",
"settings.notyf.visual.theme.install.success": "Tema berhasil dipasang", "settings.notyf.visual.theme.install.success": "Tema berhasil dipasang",
"settings.notyf.visual.theme.install.error": "Pemasangan tema gagal", "settings.notyf.visual.theme.install.error": "Pemasangan tema gagal",
"settings.header.visual.plugin": "Plugin", "settings.header.visual.plugin": "Plugin",
@ -347,17 +505,29 @@
"settings.header.window.description": "Atur pengaturan jendela pada Cider.", "settings.header.window.description": "Atur pengaturan jendela pada Cider.",
"settings.option.window.openOnStartup": "Buka Cider Ketika Perangkat Dinyalakan", "settings.option.window.openOnStartup": "Buka Cider Ketika Perangkat Dinyalakan",
"settings.option.window.openOnStartup.hidden": "Buka dalam mode tersembunyi", "settings.option.window.openOnStartup.hidden": "Buka dalam mode tersembunyi",
"settings.option.window.useNativeTitleBar": "Gunakan Title Bar Native",
"settings.option.window.windowControlStyle": "Pengontrol Style Jendela",
"settings.option.window.windowControlStyle.right": "Kanan",
"settings.option.window.windowControlStyle.left": "Kiri",
"settings.header.lyrics": "Lirik", "settings.header.lyrics": "Lirik",
"settings.header.lyrics.description": "Sesuaikan pengaturan lirik untuk Cider.", "settings.header.lyrics.description": "Sesuaikan pengaturan lirik untuk Cider.",
"settings.option.lyrics.enableMusixmatch": "Aktifkan Lirik Musixmatch", "settings.option.lyrics.enableMusixmatch": "Aktifkan Lirik Musixmatch",
"settings.option.lyrics.enableMusixmatchKaraoke": "Aktifkan Mode Karaoke (khusus Musixmatch)", "settings.option.lyrics.enableMusixmatchKaraoke": "Aktifkan Mode Karaoke (khusus Musixmatch)",
"settings.option.lyrics.musixmatchPreferredLanguage": "Bahasa Terjemahan Musixmatch", "settings.option.lyrics.musixmatchPreferredLanguage": "Bahasa Terjemahan Musixmatch",
"settings.option.lyrics.enableYoutubeLyrics": "Aktifkan Lirik Youtube untuk Video Musik", "settings.option.lyrics.enableYoutubeLyrics": "Aktifkan Lirik Youtube untuk Video Musik",
"settings.option.lyrics.enableQQLyrics": "Aktifkan QQ Lyrics",
"settings.header.connectivity": "Konektivitas", "settings.header.connectivity": "Konektivitas",
"settings.header.connectivity.description": "Sesuaikan pengaturan konektivitas untuk Cider.", "settings.header.connectivity.description": "Sesuaikan pengaturan konektivitas untuk Cider.",
"settings.option.connectivity.discordRPC": "Discord Rich Presence",
"settings.option.connectivity.playbackNotifications": "Pemberitahuan Pemutaran", "settings.option.connectivity.playbackNotifications": "Pemberitahuan Pemutaran",
"settings.option.connectivity.discordRPC": "Discord Rich Presence",
"settings.option.connectivity.discordRPC.clientName": "Nama Client",
"settings.option.connectivity.discordRPC.clearOnPause": "Sembunyikan Discord Rich Presence Saat Dijeda", "settings.option.connectivity.discordRPC.clearOnPause": "Sembunyikan Discord Rich Presence Saat Dijeda",
"settings.option.connectivity.discordRPC.hideButtons": "Sembunyikan tombol pada Discord Rich Presence",
"settings.option.connectivity.discordRPC.hideTimestamp": "Sembunyikan tanda waktu pada Discord Rich Presence",
"settings.option.connectivity.discordRPC.detailsFormat": "Format Detail",
"settings.option.connectivity.discordRPC.stateFormat": "Format State",
"settings.option.connectivity.discordRPC.reload": "Muat Ulang DiscordRPC",
"settings.option.connectivity.discordRPC.reconnectedToUser": "DiscordRPC terhubung ke pengguna: {{user}} ({{userid}})",
"settings.option.connectivity.lastfmScrobble": "Last.fm Scrobbling", "settings.option.connectivity.lastfmScrobble": "Last.fm Scrobbling",
"settings.option.connectivity.lastfmScrobble.delay": "Delay Last.fm Scrobble (%)", "settings.option.connectivity.lastfmScrobble.delay": "Delay Last.fm Scrobble (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Aktifkan Last.fm Now Playing", "settings.option.connectivity.lastfmScrobble.nowPlaying": "Aktifkan Last.fm Now Playing",
@ -368,6 +538,8 @@
"settings.option.debug.openAppData": "Buka Folder Cider", "settings.option.debug.openAppData": "Buka Folder Cider",
"settings.header.experimental": "Eksperimental", "settings.header.experimental": "Eksperimental",
"settings.header.experimental.description": "Sesuaikan pengaturan eksperimental untuk Cider.", "settings.header.experimental.description": "Sesuaikan pengaturan eksperimental untuk Cider.",
"settings.option.experimental.reinstallwidevine": "Pasang Ulang WidevineCDM",
"settings.option.experimental.reinstallwidevine.confirm": "Apakah Anda yakin untuk memasang ulang Widevine?",
"settings.option.experimental.unknownPlugin": "Sumber Tidak Diketahui", "settings.option.experimental.unknownPlugin": "Sumber Tidak Diketahui",
"settings.option.experimental.unknownPlugin.description": "Izinkan pemasangan plugin dari repo selain Cider Plugin Repository", "settings.option.experimental.unknownPlugin.description": "Izinkan pemasangan plugin dari repo selain Cider Plugin Repository",
"settings.option.experimental.compactUI": "UI Kompak", "settings.option.experimental.compactUI": "UI Kompak",
@ -377,6 +549,10 @@
"settings.option.advanced.playlistTrackMapping.description": "Mengaktifkan pemindaian daftar putar yang mendalam untuk menentukan trek mana yang ada di daftar putar. Waktu pembuatan cache daftar putar dapat meningkat secara signifikan.", "settings.option.advanced.playlistTrackMapping.description": "Mengaktifkan pemindaian daftar putar yang mendalam untuk menentukan trek mana yang ada di daftar putar. Waktu pembuatan cache daftar putar dapat meningkat secara signifikan.",
"settings.option.visual.transparent": "Frame transparan", "settings.option.visual.transparent": "Frame transparan",
"settings.option.visual.transparent.description": "membutuhkan tema yang mendukung, membutuhkan dibuka ulang", "settings.option.visual.transparent.description": "membutuhkan tema yang mendukung, membutuhkan dibuka ulang",
"settings.header.advanced": "Lanjutan",
"settings.header.connect": "Sinkron",
"settings.option.connect.link_account": "Sikronisasikan dengan Cider Connect",
"settings.option.connect.link_account.description": "Menghubungkan akun Discord Anda dengan Cider Connect memungkinkan Anda untuk menyimpan data pengguna termasuk Pengaturan, EQ, dan lebih banyak lagi setelah selesai. (Pekerjaan Dalam Proses)",
"spatial.notTurnedOn": "Spasialisasi Audio dinonaktifkan. Untuk menggunakan, aktifkan terlebih dahulu.", "spatial.notTurnedOn": "Spasialisasi Audio dinonaktifkan. Untuk menggunakan, aktifkan terlebih dahulu.",
"spatial.spatialProperties": "Spatial Properties", "spatial.spatialProperties": "Spatial Properties",
"spatial.width": "Lebar", "spatial.width": "Lebar",
@ -399,5 +575,54 @@
"settings.header.unfinished": "Belum Diselesaikan", "settings.header.unfinished": "Belum Diselesaikan",
"remote.web.title": "Remote Cider", "remote.web.title": "Remote Cider",
"remote.web.description": "Pindai kode QR untuk memasangkan ponsel Anda dengan Cider.", "remote.web.description": "Pindai kode QR untuk memasangkan ponsel Anda dengan Cider.",
"about.thanks": "Terima kasih yang sebesar-besarnya kepada Cider Collective Team dan semua kontributor kami." "share.platform.twitter.tweet": "Dengarkan {{song}} di Apple Music.\n\n{{url}}\n\n#AppleMusic #Cider",
"share.platform.twitter": "Twitter",
"share.platform.facebook": "Facebook",
"share.platform.reddit": "Reddit",
"share.platform.telegram": "Telegram",
"share.platform.whatsapp": "WhatsApp",
"share.platform.messenger": "Messenger",
"share.platform.email": "Email",
"share.platform.songLink": "Salin dengan song.link",
"share.platform.clipboard": "Salin Link",
"about.thanks": "Terima kasih yang sebesar-besarnya kepada Cider Collective Team dan semua kontributor kami.",
"oobe.yes": "Ya",
"oobe.no": "Tidak",
"oobe.next": "Berikutnya",
"oobe.previous": "Sebelumnya",
"oobe.done": "Selesai",
"oobe.amupsell.title": "Sebelum kita mulai",
"oobe.amupsell.text": "Cider memerlukan langganan Apple Music yang aktif\nCider tidak akan berfungsi dengan Apple Music Voice Plan atau beberapa langganan uji coba promosi. Jika Anda sudah berlangganan Apple Music yang memenuhi syarat, klik Berikutnya untuk melanjutkan.",
"oobe.amupsell.subscribeBtn": "Berlangganan Apple Music",
"oobe.amupsell.explainBtn": "Jelaskan",
"oobe.amupsell.subscribeUrl": "https://apple.co/3MdqJVQ",
"oobe.amupsell.amWebUrl": "https://beta.music.apple.com/",
"oobe.amupsell.promoExplained": "Beberapa promosi uji coba Apple Music dan akun non AS tidak memiliki akses ke Apple Music Web Player API yang diperlukan agar Cider berfungsi. Untuk memverifikasi apakah uji coba aktif Anda akan berfungsi dengan Cider, buka <a href='{{ amWebUrl }}'>{{ amWebUrl }}</a> masuk dan coba putar musik. Jika berhasil, bagus! Anda siap menggunakan Cider, namun jika tidak mempertimbangkan untuk berlangganan Apple Music di sini: <a href='{{ subscribeUrl }}'>{{ subscribeUrl }}</a>",
"oobe.intro.title": "Selamat Datang di Cider",
"oobe.intro.subtitle": "",
"oobe.intro.text": "Mari kita siapkan beberapa hal agar Anda dapat menggunakan Cider sesuai keinginan Anda. Anda selalu dapat mengubah setelan ini nanti.",
"oobe.general.title": "Umum",
"oobe.general.subtitle": "",
"oobe.general.text": "",
"oobe.audio.title": "Audio",
"oobe.audio.subtitle": "",
"oobe.audio.text": "Cider menghadirkan tumpukan audio yang disesuaikan dan dirancang khusus. Serta menghadirkan pengalaman audio berkualitas tinggi yang baik.\nDengan fitur seperti Cider Adrenaline, Atmosphere Realizer, dan Spatialized Audio.\nUntuk mengaktifkan fungsi ini \"Pengaturan Audio Tingkat Lanjut\" harus dinyalakan.\nMengaktifkan Pengaturan Audio Tingkat Lanjut akan memberi Anda akses ke peningkatan ini di Lab Audio Cider, yang ditemukan di pengaturan aplikasi",
"oobe.audio.advancedFunctionality": "",
"oobe.visual.title": "Visual",
"oobe.visual.subtitle": "",
"oobe.visual.text": "",
"oobe.visual.layout.text": "Cider menampilkan dua jendela tata letak yang berbeda.\nMaverick adalah tata letak seperti iTunes dengan pemutar di bagian atas jendela.\nMojave adalah putaran baru yang dibuat oleh tim Cider Collective.\n\nAnda dapat mengubah tata letak kapan saja di setelan.",
"oobe.visual.suggestingThemes": "Tema adalah cara terbaik untuk mempersonalisasi pengalaman Anda. Berikut adalah beberapa yang kami sarankan:",
"oobe.visual.suggestingThemes.subtext": "(Tema-tema ini akan diunduh dari GitHub)",
"oobe.visual.suggestingThemes.default": "Cider",
"oobe.visual.suggestingThemes.default.text": "Tema classic Cider.",
"oobe.visual.suggestingThemes.dark": "Gelap",
"oobe.visual.suggestingThemes.dark.text": "Kegelapan.",
"oobe.visual.suggestingThemes.community1": "Groovy",
"oobe.visual.suggestingThemes.community1.text": "A WinUI influenced theme",
"oobe.visual.suggestingThemes.community2": "iTheme",
"oobe.visual.suggestingThemes.community2.text": "The classic big fruit layout.",
"oobe.visual.suggestingThemes.community3": "Dracula",
"oobe.visual.suggestingThemes.community3.text": "The iconic Dracula color scheme.",
"oobe.amsignin.title": ""
} }

View file

@ -22,7 +22,6 @@
"term.logout": "Terminar sessão", "term.logout": "Terminar sessão",
"term.login": "Iniciar sessão", "term.login": "Iniciar sessão",
"term.quickNav": "Navegação rápida", "term.quickNav": "Navegação rápida",
"term.cast": "Transmitir",
"term.about": "Sobre", "term.about": "Sobre",
"term.privateSession": "Sessão privada", "term.privateSession": "Sessão privada",
"term.disablePrivateSession": "Desativar sessão privada", "term.disablePrivateSession": "Desativar sessão privada",
@ -32,6 +31,8 @@
"term.miniplayer": "Mini-leitor", "term.miniplayer": "Mini-leitor",
"term.history": "Histórico", "term.history": "Histórico",
"term.search": "Pesquisa", "term.search": "Pesquisa",
"term.showSearch": "Mostrar a barra de pesquisa",
"term.hideSearch": "Esconder a barar de pesquisa",
"term.library": "Biblioteca", "term.library": "Biblioteca",
"term.listenNow": "Ouvir agora", "term.listenNow": "Ouvir agora",
"term.browse": "Explorar", "term.browse": "Explorar",
@ -52,6 +53,7 @@
"term.navigateBack": "Retroceder uma página", "term.navigateBack": "Retroceder uma página",
"term.navigateForward": "Avançar uma página", "term.navigateForward": "Avançar uma página",
"term.play": "Reproduzir", "term.play": "Reproduzir",
"term.playpause": "Reproduzir/Pausa",
"term.pause": "Pausa", "term.pause": "Pausa",
"term.stop": "Parar", "term.stop": "Parar",
"term.previous": "Anterior", "term.previous": "Anterior",
@ -101,7 +103,7 @@
"term.less": "Menos", "term.less": "Menos",
"term.showMore": "Mostrar mais", "term.showMore": "Mostrar mais",
"term.showLess": "Mostrar menos", "term.showLess": "Mostrar menos",
"term.topSongs": "Músicas populares", "term.topSongs": "Top de músicas",
"term.latestReleases": "Últimos lançamentos", "term.latestReleases": "Últimos lançamentos",
"term.time.added": "Adicionado", "term.time.added": "Adicionado",
"term.time.released": "Lançado", "term.time.released": "Lançado",
@ -134,6 +136,7 @@
"term.recentStations": "Estações recentes", "term.recentStations": "Estações recentes",
"term.personalStations": "Estações pessoais", "term.personalStations": "Estações pessoais",
"term.amLive": "Apple Music Live", "term.amLive": "Apple Music Live",
"term.live": "AO VIVO",
"term.language": "Idioma", "term.language": "Idioma",
"term.funLanguages": "Divertido", "term.funLanguages": "Divertido",
"term.noLyrics": "A carregar... / Letra não encontrada. / Instrumental.", "term.noLyrics": "A carregar... / Letra não encontrada. / Instrumental.",
@ -154,6 +157,7 @@
}, },
"term.videos": "Vídeos", "term.videos": "Vídeos",
"term.menu": "Menu", "term.menu": "Menu",
"term.themeManaged": "Gerido por um tema",
"term.check": "Verificar", "term.check": "Verificar",
"term.aboutArtist": "Sobre {{artistName}}", "term.aboutArtist": "Sobre {{artistName}}",
"term.topResult": "Melhor resultado", "term.topResult": "Melhor resultado",
@ -184,15 +188,27 @@
"term.uniqueAlbums": "Álbuns únicos", "term.uniqueAlbums": "Álbuns únicos",
"term.uniqueArtists": "Intérpretes únicos", "term.uniqueArtists": "Intérpretes únicos",
"term.uniqueSongs": "Músicas únicas", "term.uniqueSongs": "Músicas únicas",
"term.topArtists": "Intérpretes populares", "term.topArtists": "Top de intérpretes",
"term.listenedTo": "Reproduzido:", "term.listenedTo": "Reproduzido:",
"term.times": "vezes", "term.times": "vezes",
"term.topAlbums": "Álbuns populares", "term.topAlbums": "Top de álbuns",
"term.plays": "Reproduções", "term.plays": "Reproduções",
"term.topGenres": "Géneros populares", "term.topGenres": "Top de géneros",
"term.confirmLogout": "Tem a certeza de que pretende terminar sessão?", "term.confirmLogout": "Tem a certeza de que pretende terminar sessão?",
"term.creditDesignedBy": "Concebido por ${authorUsername}", "term.creditDesignedBy": "Concebido por ${authorUsername}",
"term.discNumber": "Disco ${discNumber}", "term.discNumber": "Disco ${discNumber}",
"term.reload": "Reiniciar o Cider ?",
"term.toggleprivate": "Alternar sessão privada",
"term.webremote": "Web Remote",
"term.cast": "Transmitir",
"term.cast2": "Transmitir para dispositivos",
"term.quit": "Fechar",
"term.zoomin": "Aumentar o zoom",
"term.zoomout": "Diminuir o zoom",
"term.zoomreset": "Repor zoom",
"term.fullscreen": "Ecrã inteiro",
"home.syncFavorites": "Sincronizar favoritos",
"home.syncFavorites.gettingArtists": "A obter os artistas favoritos...",
"home.title": "Início", "home.title": "Início",
"home.recentlyPlayed": "Reproduzido recentemente", "home.recentlyPlayed": "Reproduzido recentemente",
"home.recentlyAdded": "Adicionado recentemente", "home.recentlyAdded": "Adicionado recentemente",
@ -213,6 +229,8 @@
"podcast.episodes": "Episódios", "podcast.episodes": "Episódios",
"podcast.playEpisode": "Reproduzir episódio", "podcast.playEpisode": "Reproduzir episódio",
"podcast.website": "Website do Podcast", "podcast.website": "Website do Podcast",
"action.favorite": "Adicionar favorito",
"action.removeFavorite": "Remover favorito",
"action.hideLibrary": "Ocultar biblioteca", "action.hideLibrary": "Ocultar biblioteca",
"action.showLibrary": "Mostrar biblioteca", "action.showLibrary": "Mostrar biblioteca",
"action.cut": "Cortar", "action.cut": "Cortar",
@ -265,11 +283,7 @@
"action.export": "Exportar", "action.export": "Exportar",
"action.showAlbum": "Mostrar álbum completo", "action.showAlbum": "Mostrar álbum completo",
"action.tray.minimize": "Minimizar para a bandeja", "action.tray.minimize": "Minimizar para a bandeja",
"action.tray.quit": "Fechar",
"action.tray.show": "Mostrar o Cider", "action.tray.show": "Mostrar o Cider",
"action.tray.playpause": "Reproduzir/Pausa",
"action.tray.next": "Seguinte",
"action.tray.previous": "Anterior",
"action.tray.listento": "Ouvir:", "action.tray.listento": "Ouvir:",
"action.update": "Atualizar", "action.update": "Atualizar",
"action.install": "Instalar", "action.install": "Instalar",
@ -289,45 +303,26 @@
"action.createNew": "Criar nova...", "action.createNew": "Criar nova...",
"action.openArtworkInBrowser": "Abrir grafismo no navegador", "action.openArtworkInBrowser": "Abrir grafismo no navegador",
"action.scrollToTop": "Voltar ao topo", "action.scrollToTop": "Voltar ao topo",
"menubar.options.about": "Sobre",
"menubar.options.settings": "Definições",
"menubar.options.quit": "Fechar o Cider",
"menubar.options.view": "Ver", "menubar.options.view": "Ver",
"menubar.options.reload": "Atualizar", "menubar.options.reload": "Atualizar",
"menubar.options.forcereload": "Forçar atualização", "menubar.options.forcereload": "Forçar atualização",
"menubar.options.toggledevtools": "Alternar ferramentas do programador", "menubar.options.toggledevtools": "Alternar ferramentas do programador",
"menubar.options.window": "Janela", "menubar.options.window": "Janela",
"menubar.options.minimize": "Minimizar", "menubar.options.minimize": "Minimizar",
"menubar.options.toggleprivate": "Alternar sessão privada",
"menubar.options.webremote": "Web Remote",
"menubar.options.audio": "Definições de áudio",
"menubar.options.plugins": "Menu de plug-ins", "menubar.options.plugins": "Menu de plug-ins",
"menubar.options.controls": "Controlos", "menubar.options.controls": "Controlos",
"menubar.options.next": "Seguinte",
"menubar.options.playpause": "Reproduzir/Pausa",
"menubar.options.previous": "Anterior",
"menubar.options.volumeup": "Aumentar o volume", "menubar.options.volumeup": "Aumentar o volume",
"menubar.options.volumedown": "Diminuir o volume", "menubar.options.volumedown": "Diminuir o volume",
"menubar.options.browse": "Explorar",
"menubar.options.artists": "Intérpretes",
"menubar.options.search": "Pesquisa",
"menubar.options.albums": "Álbuns",
"menubar.options.cast": "Transmitir para dispositivos",
"menubar.options.account": "Conta", "menubar.options.account": "Conta",
"menubar.options.accountsettings": "Definições da conta",
"menubar.options.signout": "Terminar sessão", "menubar.options.signout": "Terminar sessão",
"menubar.options.support": "Suporte", "menubar.options.support": "Suporte",
"menubar.options.discord": "Discord",
"menubar.options.github": "GitHub Wiki",
"menubar.options.report": "Reportar um...", "menubar.options.report": "Reportar um...",
"menubar.options.bug": "Problema", "menubar.options.bug": "Problema",
"menubar.options.feature": "Pedido de funcionalidade", "menubar.options.feature": "Pedido de funcionalidade",
"menubar.options.trans": "Pedido de tradução", "menubar.options.trans": "Pedido de tradução",
"menubar.options.license": "Ver licença", "menubar.options.license": "Ver licença",
"menubar.options.conf": "Abrir ficheiro de configuração no editor", "menubar.options.conf": "Abrir ficheiro de configuração no editor",
"menubar.options.listennow": "Ouvir agora", "menubar.options.zoom": "Zoom",
"menubar.options.recentlyAdded": "Adições recentes",
"menubar.options.songs": "Músicas",
"settings.header.general": "Geral", "settings.header.general": "Geral",
"settings.header.general.description": "Ajustar as definições gerais do Cider.", "settings.header.general.description": "Ajustar as definições gerais do Cider.",
"settings.option.general.language": "Idioma", "settings.option.general.language": "Idioma",
@ -347,11 +342,15 @@
"settings.option.general.customizeSidebar": "Personalizar os itens da barra lateral", "settings.option.general.customizeSidebar": "Personalizar os itens da barra lateral",
"settings.option.general.customizeSidebar.customize": "Personalizar", "settings.option.general.customizeSidebar.customize": "Personalizar",
"settings.option.general.keybindings": "Atalhos do teclado", "settings.option.general.keybindings": "Atalhos do teclado",
"settings.option.general.keybindings.library": "Biblioteca",
"settings.option.general.keybindings.session": "Sessão",
"settings.option.general.keybindings.control": "Controlos",
"settings.option.general.keybindings.interface": "Interface",
"settings.option.general.keybindings.advanced": "Avançado",
"settings.option.general.keybindings.pressCombination": "Prima uma combinação de duas teclas para atualizar o atalho.", "settings.option.general.keybindings.pressCombination": "Prima uma combinação de duas teclas para atualizar o atalho.",
"settings.option.general.keybindings.pressEscape": "Prima a tecla Escape para voltar atrás.", "settings.option.general.keybindings.pressEscape": "Prima a tecla Escape para voltar atrás.",
"settings.notyf.general.keybindings.update.success": "Atalho atualizado com sucesso", "settings.notyf.general.keybindings.update.success": "Atalho atualizado com sucesso",
"settings.prompt.general.keybindings.update.success": "Atalho atualizado com sucesso. Prima OK para reiniciar o Cider", "settings.prompt.general.keybindings.update.success": "Atalho atualizado com sucesso. Prima OK para reiniciar o Cider",
"settings.option.general.keybindings.open": "Abrir",
"settings.option.general.themeUpdateNotification": "Verificação automática de atualizações de temas", "settings.option.general.themeUpdateNotification": "Verificação automática de atualizações de temas",
"settings.option.general.showLovedTracksInline": "Mostrar músicas que gosta \"inline\"", "settings.option.general.showLovedTracksInline": "Mostrar músicas que gosta \"inline\"",
"settings.description.search": "Pesquisa", "settings.description.search": "Pesquisa",
@ -413,7 +412,9 @@
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E68_2": "Uji Matcha Milk Tea", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E68_2": "Uji Matcha Milk Tea",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E168_1": "Jasmine Macchiato", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E168_1": "Jasmine Macchiato",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z3600": "Hokkaido Milk Tea", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z3600": "Hokkaido Milk Tea",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500": "Moonlight Softcake", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500A": "Moonwight Softcake",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500B": "Clafoutis aux Cerises",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500C": "Uji Matcha Mochi",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.BSCBM": "Brown Sugar Creme Brûlée Milk", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.BSCBM": "Brown Sugar Creme Brûlée Milk",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.CUDDLE": "Cuddle Warmth", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.CUDDLE": "Cuddle Warmth",
"settings.option.audio.enableAdvancedFunctionality.ciderPPE": "Cider Adrenaline Processor™", "settings.option.audio.enableAdvancedFunctionality.ciderPPE": "Cider Adrenaline Processor™",
@ -448,6 +449,11 @@
"settings.header.visual": "Aparência", "settings.header.visual": "Aparência",
"settings.header.visual.description": "Ajustar as definições visuais do Cider.", "settings.header.visual.description": "Ajustar as definições visuais do Cider.",
"settings.option.visual.windowStyle": "Estilo da janela", "settings.option.visual.windowStyle": "Estilo da janela",
"settings.option.visual.customAccentColor": "Cor de destaque personalizada",
"settings.option.visual.accentColor": "Cor de destaque",
"settings.option.visual.purplePodcastPlaybackBar": "Barra de reprodução roxa para Podcasts",
"settings.option.visual.windowColor": "Tonalidade da cor da janela",
"settings.header.visual.windowBackgroundStyle.color": "Tonalidade da cor",
"settings.option.visual.windowBackgroundStyle": "Estilo do fundo da janela", "settings.option.visual.windowBackgroundStyle": "Estilo do fundo da janela",
"settings.header.visual.windowBackgroundStyle.none": "Nenhum", "settings.header.visual.windowBackgroundStyle.none": "Nenhum",
"settings.header.visual.windowBackgroundStyle.artwork": "Grafismo", "settings.header.visual.windowBackgroundStyle.artwork": "Grafismo",

View file

@ -31,6 +31,12 @@
"term.miniplayer": "MiniPlayer", "term.miniplayer": "MiniPlayer",
"term.history": "History", "term.history": "History",
"term.search": "Search", "term.search": "Search",
"term.scroll": "Scroll Mode",
"term.scroll.infinite": "Infinite",
"term.scroll.paged": "${songsPerPage} per page",
"term.live": "LIVE",
"term.showSearch": "Show search bar",
"term.hideSearch": "Hide search bar",
"term.library": "Library", "term.library": "Library",
"term.listenNow": "Listen Now", "term.listenNow": "Listen Now",
"term.browse": "Browse", "term.browse": "Browse",
@ -136,7 +142,7 @@
"term.amLive": "Apple Music Live", "term.amLive": "Apple Music Live",
"term.language": "Language", "term.language": "Language",
"term.funLanguages": "Fun", "term.funLanguages": "Fun",
"term.noLyrics": "Loading... / Lyrics not found./ Instrumental.", "term.noLyrics": "Instrumental Track / No Lyrics.",
"term.copyright": "Copyright", "term.copyright": "Copyright",
"term.rightsReserved": "All Rights Reserved.", "term.rightsReserved": "All Rights Reserved.",
"term.sponsor": "Sponsor this project", "term.sponsor": "Sponsor this project",
@ -178,8 +184,9 @@
"term.top": "Top", "term.top": "Top",
"term.version": "Version", "term.version": "Version",
"term.noVideos": "No videos found.", "term.noVideos": "No videos found.",
"term.plugin": "Plug-in", "term.plugins": "Plugins",
"term.pluginMenu": "Plug-in Menu", "term.plugin": "Plugin",
"term.pluginMenu": "Plugins Menu",
"term.pluginMenu.none": "No interactive plugins", "term.pluginMenu.none": "No interactive plugins",
"term.replay": "Replay", "term.replay": "Replay",
"term.uniqueAlbums": "Unique Albums", "term.uniqueAlbums": "Unique Albums",
@ -194,7 +201,7 @@
"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",
@ -204,6 +211,12 @@
"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",
"home.syncFavorites": "Sync Favorites",
"home.syncFavorites.gettingArtists": "Getting Favorited Artists...",
"action.favorite": "Favorite",
"action.removeFavorite": "Remove Favorite",
"action.refresh": "Refresh",
"home.title": "Home", "home.title": "Home",
"home.recentlyPlayed": "Recently Played", "home.recentlyPlayed": "Recently Played",
"home.recentlyAdded": "Recently Added", "home.recentlyAdded": "Recently Added",
@ -232,6 +245,7 @@
"action.delete": "Delete", "action.delete": "Delete",
"action.edit": "Edit", "action.edit": "Edit",
"action.done": "Done", "action.done": "Done",
"action.submit": "Submit",
"action.editTracklist": "Edit Tracklist", "action.editTracklist": "Edit Tracklist",
"action.addToLibrary": "Add to Library", "action.addToLibrary": "Add to Library",
"action.addToLibrary.success": "Added to Library", "action.addToLibrary.success": "Added to Library",
@ -407,6 +421,8 @@
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z3600": "Hokkaido Milk Tea", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z3600": "Hokkaido Milk Tea",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500A": "Moonlight Softcake", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500A": "Moonlight Softcake",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.BSCBM": "Brown Sugar Creme Brûlée Milk", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.BSCBM": "Brown Sugar Creme Brûlée Milk",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500B": "Clafoutis aux Cerises",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500C": "Uji Matcha Mochi",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.CUDDLE": "Cuddle Warmth", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.CUDDLE": "Cuddle Warmth",
"settings.option.audio.enableAdvancedFunctionality.ciderPPE": "Cider Adrenaline Processor™", "settings.option.audio.enableAdvancedFunctionality.ciderPPE": "Cider Adrenaline Processor™",
"settings.option.audio.enableAdvancedFunctionality.ciderPPE.description": "Enhances the perceived audio quality of AAC encoded audio by using a real-time algorithm that takes advantage of both psychoacoustic models of human hearing and AAC encoding characteristics.", "settings.option.audio.enableAdvancedFunctionality.ciderPPE.description": "Enhances the perceived audio quality of AAC encoded audio by using a real-time algorithm that takes advantage of both psychoacoustic models of human hearing and AAC encoding characteristics.",
@ -469,6 +485,7 @@
"settings.prompt.visual.theme.github.URL": "Enter the URL of the theme you want to install", "settings.prompt.visual.theme.github.URL": "Enter the URL of the theme you want to install",
"settings.prompt.visual.theme.uninstallTheme": "Are you sure you want to uninstall {{ theme }}?", "settings.prompt.visual.theme.uninstallTheme": "Are you sure you want to uninstall {{ theme }}?",
"settings.option.visual.theme.checkForUpdates": "Check for updates", "settings.option.visual.theme.checkForUpdates": "Check for updates",
"settings.header.visual.styles": "Styles",
"settings.option.visual.theme.manageStyles": "Manage Styles", "settings.option.visual.theme.manageStyles": "Manage Styles",
"settings.option.visual.theme.uninstall": "Uninstall", "settings.option.visual.theme.uninstall": "Uninstall",
"settings.option.visual.theme.viewInfo": "View Info", "settings.option.visual.theme.viewInfo": "View Info",
@ -520,6 +537,12 @@
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Enable Last.fm Now Playing", "settings.option.connectivity.lastfmScrobble.nowPlaying": "Enable Last.fm Now Playing",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Remove featuring artists from song title (Last.fm)", "settings.option.connectivity.lastfmScrobble.removeFeatured": "Remove featuring artists from song title (Last.fm)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (Last.fm)", "settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (Last.fm)",
"settings.option.connectivity.lastfmScrobble.filterLoop.description": "Prevent looped tracks from being scrobbled or displayed in the Now Playing list on Last.fm.",
"settings.option.connectivity.lastfmScrobble.filterTypes": "Filter Media Types (Last.fm)",
"settings.option.connectivity.lastfmScrobble.manualToken": "Enter Last.fm Token Manually",
"settings.notyf.connectivity.lastfmScrobble.connectError": "Last.fm Connection Timed Out",
"settings.notyf.connectivity.lastfmScrobble.connectSuccess": "Last.fm Connection Successful",
"settings.notyf.connectivity.lastfmScrobble.connecting": "Connecting to Last.fm...",
"settings.header.debug": "Debug", "settings.header.debug": "Debug",
"settings.option.debug.copy_log": "Copy logs to clipboard", "settings.option.debug.copy_log": "Copy logs to clipboard",
"settings.option.debug.openAppData": "Open Cider Folder", "settings.option.debug.openAppData": "Open Cider Folder",
@ -531,11 +554,17 @@
"settings.option.experimental.unknownPlugin.description": "Allow installation of plugins from repos other than the Cider Plugin Repository", "settings.option.experimental.unknownPlugin.description": "Allow installation of plugins from repos other than the Cider Plugin Repository",
"settings.option.experimental.compactUI": "Compact UI", "settings.option.experimental.compactUI": "Compact UI",
"settings.option.window.close_button_hide": "Close Button Should Hide the Application", "settings.option.window.close_button_hide": "Close Button Should Hide the Application",
"settings.option.window.maxElementScale": "Maximum Element Scale",
"settings.option.experimental.inline_playlists": "Inline Playlists and Albums", "settings.option.experimental.inline_playlists": "Inline Playlists and Albums",
"settings.option.advanced.playlistTrackMapping": "Playlist Track Mapping", "settings.option.advanced.playlistTrackMapping": "Playlist Track Mapping",
"settings.option.advanced.playlistTrackMapping.description": "Enables deep scanning of playlists to determine which tracks are in which playlists. Playlist cache build times can increase significantly.", "settings.option.advanced.playlistTrackMapping.description": "Enables deep scanning of playlists to determine which tracks are in which playlists. Playlist cache build times can increase significantly.",
"settings.option.visual.transparent": "Transparent frame", "settings.option.visual.transparent": "Transparent frame",
"settings.option.visual.transparent.description": "needs Theme Support, requires relaunch", "settings.option.visual.transparent.description": "needs Theme Support, requires relaunch",
"settings.option.visual.customAccentColor": "Custom Accent Color",
"settings.option.visual.accentColor": "Accent Color",
"settings.option.visual.purplePodcastPlaybackBar": "Purple Playback Bar for Podcasts",
"settings.option.visual.windowColor": "Window Tint Color",
"settings.header.visual.windowBackgroundStyle.color": "Color Tint",
"settings.header.advanced": "Advanced", "settings.header.advanced": "Advanced",
"settings.header.connect": "Sync", "settings.header.connect": "Sync",
"settings.option.connect.link_account": "Enable Sync with Cider Connect", "settings.option.connect.link_account": "Enable Sync with Cider Connect",

View file

@ -2,7 +2,7 @@
"i18n.languageName": "简体中文(中国)", "i18n.languageName": "简体中文(中国)",
"i18n.languageNameEnglish": "Simp. Chinese (China)", "i18n.languageNameEnglish": "Simp. Chinese (China)",
"i18n.category": "main", "i18n.category": "main",
"i18n.authors": "@notmaikiwi @BillKerman @jay900604", "i18n.authors": "@notmaikiwi @BillKerman @jay900604 @sakura0224",
"app.name": "Cider", "app.name": "Cider",
"date.format": "${y}年${m}月${d}日", "date.format": "${y}年${m}月${d}日",
"dialog.cancel": "取消", "dialog.cancel": "取消",
@ -21,17 +21,22 @@
"term.accountSettings": "账户设置", "term.accountSettings": "账户设置",
"term.logout": "退出登录", "term.logout": "退出登录",
"term.login": "登录", "term.login": "登录",
"term.quickNav":"快速导航", "term.quickNav": "快速导航",
"term.cast":"投射",
"term.about": "关于", "term.about": "关于",
"term.privateSession": "隐身聆听", "term.privateSession": "隐身聆听",
"term.disablePrivateSession":"停止隐身聆听", "term.disablePrivateSession": "停止隐身聆听",
"term.autoplay":"自动播放",
"term.lyrics": "歌词",
"term.queue": "待播清单", "term.queue": "待播清单",
"term.history": "历史记录", "term.autoplay": "自动播放",
"term.lyrics": "歌词",
"term.miniplayer": "迷你播放器", "term.miniplayer": "迷你播放器",
"term.history": "历史记录",
"term.search": "搜索", "term.search": "搜索",
"term.scroll": "滚动模式",
"term.scroll.infinite": "无限制",
"term.scroll.paged": "每页${songsPerPage}首",
"term.live": "LIVE",
"term.showSearch": "显示搜索栏",
"term.hideSearch": "隐藏搜索栏",
"term.library": "资料库", "term.library": "资料库",
"term.listenNow": "现在就听", "term.listenNow": "现在就听",
"term.browse": "浏览", "term.browse": "浏览",
@ -42,23 +47,24 @@
"term.artists": "艺人", "term.artists": "艺人",
"term.podcasts": "播客", "term.podcasts": "播客",
"term.playlists": "播放列表", "term.playlists": "播放列表",
"term.charts":"排行榜", "term.charts": "排行榜",
"term.playlist": "播放列表", "term.playlist": "播放列表",
"term.newPlaylist": "新播放列表", "term.newPlaylist": "新播放列表",
"term.newPlaylistFolder": "新播放列表文件夹", "term.newPlaylistFolder": "新播放列表文件夹",
"term.createNewPlaylist": "新建播放列表", "term.createNewPlaylist": "新建播放列表",
"term.createNewPlaylistFolder": "新建播放列表文件夹", "term.createNewPlaylistFolder": "新建播放列表文件夹",
"term.deletePlaylist": "您确定要删除该播放列表吗?", "term.deletePlaylist": "您确定要删除该播放列表吗?",
"term.navigateBack":"上一页", "term.navigateBack": "上一页",
"term.navigateForward":"下一页", "term.navigateForward": "下一页",
"term.play": "播放", "term.play": "播放",
"term.playpause": "播放/暂停",
"term.pause": "暂停", "term.pause": "暂停",
"term.stop": "停止", "term.stop": "停止",
"term.previous": "上一首", "term.previous": "上一首",
"term.next": "下一首", "term.next": "下一首",
"term.shuffle": "随机播放", "term.shuffle": "随机播放",
"term.enableShuffle":"开启随机播放", "term.enableShuffle": "开启随机播放",
"term.disableShuffle":"关闭随机播放", "term.disableShuffle": "关闭随机播放",
"term.repeat": "循环播放", "term.repeat": "循环播放",
"term.enableRepeatOne": "开启单曲循环", "term.enableRepeatOne": "开启单曲循环",
"term.disableRepeatOne": "关闭单曲循环", "term.disableRepeatOne": "关闭单曲循环",
@ -67,7 +73,7 @@
"term.mute": "静音", "term.mute": "静音",
"term.unmute": "解除静音", "term.unmute": "解除静音",
"term.share": "分享", "term.share": "分享",
"term.share.success": "已拷到剪贴板", "term.share.success": "已拷到剪贴板",
"term.settings": "设置", "term.settings": "设置",
"term.seeAll": "查看全部", "term.seeAll": "查看全部",
"term.sortBy": "排序", "term.sortBy": "排序",
@ -77,14 +83,14 @@
"term.sortBy.genre": "类型", "term.sortBy.genre": "类型",
"term.sortBy.releaseDate": "发行日期", "term.sortBy.releaseDate": "发行日期",
"term.sortBy.duration": "时长", "term.sortBy.duration": "时长",
"term.sortBy.dateAdded":"加入日期", "term.sortBy.dateAdded": "加入日期",
"term.sortOrder": "字母排序", "term.sortOrder": "字母排序",
"term.sortOrder.ascending": "升序", "term.sortOrder.ascending": "升序",
"term.sortOrder.descending": "倒序", "term.sortOrder.descending": "倒序",
"term.viewAs": "显示模式", "term.viewAs": "显示模式",
"term.viewAs.coverArt": "专辑封面", "term.viewAs.coverArt": "专辑封面",
"term.viewAs.list": "列表", "term.viewAs.list": "列表",
"term.dynamic":"动态", "term.dynamic": "动态",
"term.size": "大小", "term.size": "大小",
"term.size.normal": "正常", "term.size.normal": "正常",
"term.size.compact": "紧凑", "term.size.compact": "紧凑",
@ -106,6 +112,8 @@
"term.time.added": "添加于", "term.time.added": "添加于",
"term.time.released": "发行于", "term.time.released": "发行于",
"term.time.updated": "更新于", "term.time.updated": "更新于",
"term.time.days": "天",
"term.time.day": "天",
"term.time.hours": "小时", "term.time.hours": "小时",
"term.time.hour": "小时", "term.time.hour": "小时",
"term.time.minutes": "分钟", "term.time.minutes": "分钟",
@ -118,8 +126,8 @@
"term.audioSettings": "音频设置", "term.audioSettings": "音频设置",
"term.clearAll": "清空", "term.clearAll": "清空",
"term.recentStations": "最近播放的广播", "term.recentStations": "最近播放的广播",
"term.personalStations":"最近播放的个人广播", "term.personalStations": "最近播放的个人广播",
"term.amLive":"amLive", "term.amLive": "Apple Music Live",
"term.language": "语言", "term.language": "语言",
"term.funLanguages": "恶搞", "term.funLanguages": "恶搞",
"term.noLyrics": "加载中... / 无搜索结果 / 纯音乐", "term.noLyrics": "加载中... / 无搜索结果 / 纯音乐",
@ -133,13 +141,11 @@
"term.contributors": "贡献者", "term.contributors": "贡献者",
"term.equalizer": "均衡器", "term.equalizer": "均衡器",
"term.reset": "重置", "term.reset": "重置",
"term.track": {
"one": "首歌曲",
"other": "首歌曲"
},
"term.tracks": "歌曲", "term.tracks": "歌曲",
"term.track": "首歌曲",
"term.videos": "音乐视频", "term.videos": "音乐视频",
"term.menu": "菜单", "term.menu": "菜单",
"term.themeManaged": "由主题所管理",
"term.check": "检查", "term.check": "检查",
"term.aboutArtist": "关于{{artistName}}", "term.aboutArtist": "关于{{artistName}}",
"term.topResult": "热门搜索结果", "term.topResult": "热门搜索结果",
@ -155,30 +161,47 @@
"term.song.link.generate": "获取 song.link 共享链接...", "term.song.link.generate": "获取 song.link 共享链接...",
"term.musicVideos": "音乐视频", "term.musicVideos": "音乐视频",
"term.stations": "电台", "term.stations": "电台",
"term.curators": "策展人",
"term.appleCurators": "Apple 策展人",
"term.radioShows": "广播单集", "term.radioShows": "广播单集",
"term.recordLabels": "唱片公司", "term.recordLabels": "唱片公司",
"term.videoExtras": "视频特辑", "term.videoExtras": "视频特辑",
"term.top":"顶部", "term.top": "顶部",
"term.version": "版本", "term.version": "版本",
"term.noVideos":"无视频", "term.noVideos": "无视频",
"term.plugins": "插件",
"term.plugin": "插件", "term.plugin": "插件",
"term.pluginMenu": "插件菜单", "term.pluginMenu": "插件菜单",
"term.pluginMenu.none": "沒有交互式插件", "term.pluginMenu.none": "沒有交互式插件",
"term.replay":"音乐回忆", "term.replay": "音乐回忆",
"term.uniqueAlbums":"Unique Albums", "term.uniqueAlbums": "独特专辑",
"term.uniqueArtists":"Unique Artists", "term.uniqueArtists": "超绝艺人",
"term.uniqueSongs":"Unique Songs", "term.uniqueSongs": "别致单曲",
"term.topArtists":"热门艺人", "term.topArtists": "热门艺人",
"term.listenedTo":"听过", "term.listenedTo": "听过",
"term.times":"次", "term.times": "次",
"term.topAlbums":"热门专辑", "term.topAlbums": "热门专辑",
"term.plays":"次", "term.plays": "次",
"term.topGenres":"热门类型", "term.topGenres": "热门类型",
"term.confirmLogout":"你确定要退出登录吗?", "term.confirmLogout": "你确定要退出登录吗?",
"term.creditDesignedBy":"由 ${authorUsername} 设计", "term.creditDesignedBy": "由 ${authorUsername} 设计",
"term.discNumber":"碟 ${discNumber}", "term.discNumber": "碟 ${discNumber}",
"term.reload" : "重新载入 Cider?",
"term.toggleprivate": "切换隐身聆听",
"term.webremote": "远程控制",
"term.cast": "投射",
"term.cast2" : "投射到设备",
"term.quit" : "退出应用",
"term.zoomin" : "放大",
"term.zoomout" : "缩小",
"term.zoomreset" : "重置缩放",
"term.fullscreen" : "全屏模式",
"term.nowPlaying": "正在播放",
"home.syncFavorites": "同步喜爱艺人", "home.syncFavorites": "同步喜爱艺人",
"home.syncFavorites.gettingArtists": "获取喜爱艺人...", "home.syncFavorites.gettingArtists": "获取喜爱艺人...",
"action.favorite": "喜爱",
"action.removeFavorite": "取消喜爱",
"action.refresh": "刷新",
"home.title": "主页", "home.title": "主页",
"home.recentlyPlayed": "最近播放", "home.recentlyPlayed": "最近播放",
"home.recentlyAdded": "最近添加", "home.recentlyAdded": "最近添加",
@ -199,16 +222,15 @@
"podcast.episodes": "单集", "podcast.episodes": "单集",
"podcast.playEpisode": "播放单集", "podcast.playEpisode": "播放单集",
"podcast.website": "Podcast 网站", "podcast.website": "Podcast 网站",
"action.favorite":"喜爱", "action.hideLibrary": "隐藏资料库",
"action.removeFavorite":"取消喜爱", "action.showLibrary": "显示资料库",
"action.hideLibrary":"隐藏资料库", "action.cut": "剪切",
"action.showLibrary":"显示资料可查", "action.paste": "粘贴",
"action.cut":"剪切", "action.selectAll": "全选",
"action.paste":"粘贴", "action.delete": "删除",
"action.selectAll":"全选",
"action.delete":"删除",
"action.edit": "编辑", "action.edit": "编辑",
"action.done": "完成", "action.done": "完成",
"action.submit": "提交",
"action.editTracklist": "编辑歌曲清单", "action.editTracklist": "编辑歌曲清单",
"action.addToLibrary": "加入资料库", "action.addToLibrary": "加入资料库",
"action.addToLibrary.success": "成功加入资料库", "action.addToLibrary.success": "成功加入资料库",
@ -224,7 +246,7 @@
"action.createPlaylist": "新建播放列表", "action.createPlaylist": "新建播放列表",
"action.addToPlaylist": "添加到播放列表", "action.addToPlaylist": "添加到播放列表",
"action.removeFromPlaylist": "从播放列表移除", "action.removeFromPlaylist": "从播放列表移除",
"action.addToFavorites": "加至收藏", "action.addToFavorites": "加至收藏",
"action.follow": "关注", "action.follow": "关注",
"action.follow.success": "已关注", "action.follow.success": "已关注",
"action.follow.error": "尝试关注的过程发生了错误", "action.follow.error": "尝试关注的过程发生了错误",
@ -236,7 +258,7 @@
"action.startRadio": "开始电台", "action.startRadio": "开始电台",
"action.goToArtist": "前往艺人", "action.goToArtist": "前往艺人",
"action.goToAlbum": "前往专辑", "action.goToAlbum": "前往专辑",
"action.showInPlaylist":"在播放列表中显示", "action.showInPlaylist": "在播放列表中显示",
"action.showInAppleMusic": "在 Apple Music 中显示", "action.showInAppleMusic": "在 Apple Music 中显示",
"action.moveToTop": "移到顶部", "action.moveToTop": "移到顶部",
"action.share": "分享歌曲", "action.share": "分享歌曲",
@ -253,11 +275,7 @@
"action.export": "导出", "action.export": "导出",
"action.showAlbum": "显示专辑", "action.showAlbum": "显示专辑",
"action.tray.minimize": "最小化", "action.tray.minimize": "最小化",
"action.tray.quit": "退出",
"action.tray.show": "显示 Cider", "action.tray.show": "显示 Cider",
"action.tray.playpause": "播放/暂停",
"action.tray.next": "下一首",
"action.tray.previous": "上一首",
"action.tray.listento": "Listen To:", "action.tray.listento": "Listen To:",
"action.update": "更新", "action.update": "更新",
"action.install": "安装", "action.install": "安装",
@ -266,7 +284,7 @@
"action.deletepreset": "删除默认", "action.deletepreset": "删除默认",
"action.open": "打开", "action.open": "打开",
"action.close": "关闭", "action.close": "关闭",
"action.relaunch.confirm":"你想重新启动 Cider 吗?", "action.relaunch.confirm": "你想重新启动 Cider 吗?",
"action.cast.chromecast": "Chromecast", "action.cast.chromecast": "Chromecast",
"action.cast.todevices": "投射到设备", "action.cast.todevices": "投射到设备",
"action.cast.stop": "停止投射到所有设备", "action.cast.stop": "停止投射到所有设备",
@ -277,49 +295,29 @@
"action.createNew": "添加...", "action.createNew": "添加...",
"action.openArtworkInBrowser": "在浏览器中打开专辑封面", "action.openArtworkInBrowser": "在浏览器中打开专辑封面",
"action.scrollToTop": "回到顶部", "action.scrollToTop": "回到顶部",
"menubar.options.about": "关于",
"menubar.options.settings": "设置",
"menubar.options.quit": "退出 Cider",
"menubar.options.view": "查看 ", "menubar.options.view": "查看 ",
"menubar.options.reload": "重新载入", "menubar.options.reload": "重新载入",
"menubar.options.forcereload": "强制重新载入", "menubar.options.forcereload": "强制重新载入",
"menubar.options.toggledevtools": "切换开发人员工具", "menubar.options.toggledevtools": "切换开发人员工具",
"menubar.options.window": "窗口", "menubar.options.window": "窗口",
"menubar.options.minimize": "最小化", "menubar.options.minimize": "最小化",
"menubar.options.toggleprivate": "切换隐身聆听",
"menubar.options.webremote": "远程控制",
"menubar.options.audio": "音频设定",
"menubar.options.plugins": "插件目录", "menubar.options.plugins": "插件目录",
"menubar.options.controls": "控制", "menubar.options.controls": "控制",
"menubar.options.next": "下一首",
"menubar.options.playpause": "播放/暂停",
"menubar.options.previous": "上一首",
"menubar.options.volumeup": "增大音量", "menubar.options.volumeup": "增大音量",
"menubar.options.volumedown": "减小音量", "menubar.options.volumedown": "减小音量",
"menubar.options.browse": "浏览",
"menubar.options.artists": "艺人",
"menubar.options.search": "搜索",
"menubar.options.albums": "专辑",
"menubar.options.cast": "投射至设备",
"menubar.options.account": "账户", "menubar.options.account": "账户",
"menubar.options.accountsettings": "账户设置",
"menubar.options.signout": "注销", "menubar.options.signout": "注销",
"menubar.options.support": "支持", "menubar.options.support": "支持",
"menubar.options.discord": "Discord",
"menubar.options.github": "GitHub Wiki",
"menubar.options.report": "报告...", "menubar.options.report": "报告...",
"menubar.options.bug": "Bug", "menubar.options.bug": "Bug",
"menubar.options.feature": "功能请求", "menubar.options.feature": "功能请求",
"menubar.options.trans": "翻译报告/请求", "menubar.options.trans": "翻译报告/请求",
"menubar.options.license": "查看授权", "menubar.options.license": "查看授权",
"menubar.options.conf": "在编辑器打开配置文件", "menubar.options.conf": "在编辑器打开配置文件",
"menubar.options.listennow": "现在就听", "menubar.options.zoom": "缩放",
"menubar.options.recentlyAdded": "最近添加",
"menubar.options.songs": "歌曲",
"settings.header.general": "通用", "settings.header.general": "通用",
"settings.header.general.description": "调整 Cider 的通用设置", "settings.header.general.description": "调整 Cider 的通用设置",
"settings.option.audio.volumeStep": "音量改变量", "settings.option.general.language": "语言",
"settings.option.audio.maxVolume": "最大音量",
"settings.option.general.resumebehavior": "恢复行为", "settings.option.general.resumebehavior": "恢复行为",
"settings.option.general.resumebehavior.description": "会影响你回到 Cider 应用程序时,恢复歌曲的方式。", "settings.option.general.resumebehavior.description": "会影响你回到 Cider 应用程序时,恢复歌曲的方式。",
"settings.option.general.resumebehavior.locally": "本地", "settings.option.general.resumebehavior.locally": "本地",
@ -330,61 +328,66 @@
"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": "语言",
"settings.option.general.language.main": "语言", "settings.option.general.language.main": "语言",
"settings.option.general.language.fun": "恶搞语言", "settings.option.general.language.fun": "恶搞语言",
"settings.option.general.language.unsorted": "未分类", "settings.option.general.language.unsorted": "未分类",
"settings.option.general.customizeSidebar": "自定义侧边栏的功能", "settings.option.general.customizeSidebar": "自定义侧边栏的功能",
"settings.option.general.customizeSidebar.customize": "自定义", "settings.option.general.customizeSidebar.customize": "自定义",
"settings.option.general.keybindings": "快捷操作键", "settings.option.general.keybindings": "快捷操作键",
"settings.option.general.keybindings.pressCombination":"按下两个键组合来更新操作设定。", "settings.option.general.keybindings.library": "资料库",
"settings.option.general.keybindings.session": "聆听",
"settings.option.general.keybindings.control": "控制",
"settings.option.general.keybindings.interface": "界面",
"settings.option.general.keybindings.advanced": "高级",
"settings.option.general.keybindings.pressCombination": "按下两个键组合来更新操作设定。",
"settings.option.general.keybindings.pressEscape": "按下 Esc 键返回。", "settings.option.general.keybindings.pressEscape": "按下 Esc 键返回。",
"settings.notyf.general.keybindings.update.success":"快捷键更新成功。", "settings.notyf.general.keybindings.update.success": "快捷键更新成功。",
"settings.prompt.general.keybindings.update.success":"快捷键更新成功,按下 OK 重新启动 Cider.", "settings.prompt.general.keybindings.update.success": "快捷键更新成功,按下 OK 重新启动 Cider。",
"settings.option.general.keybindings.open": "打开", "settings.option.general.themeUpdateNotification": "自动检查主题更新",
"settings.option.general.themeUpdateNotification":"自动检查主题更新", "settings.option.general.showLovedTracksInline": "行内显示喜爱曲目",
"settings.option.general.showLovedTracksInline":"行内显示喜爱曲目", "settings.description.search": "搜索",
"settings.description.search":"搜索", "settings.description.albums": "资料库专辑",
"settings.description.albums":"资料库专辑", "settings.description.artists": "资料库艺人",
"settings.description.artists":"资料库艺人", "settings.description.browse": "浏览",
"settings.description.browse":"浏览", "settings.description.private": "隐身聆听",
"settings.description.private":"隐身聆听", "settings.description.remote": "远程控制",
"settings.description.remote":"远程控制", "settings.description.audio": "音频设定",
"settings.description.audio":"音频设定", "settings.description.plugins": "插件目录",
"settings.description.plugins":"插件目录", "settings.description.cast": "投射到装置",
"settings.description.cast":"投射到装置", "settings.description.settings": "设置",
"settings.description.settings":"设置", "settings.description.developer": "开发者",
"settings.description.developer":"开发者", "settings.description.listnow": "现在就听",
"settings.description.listnow":"现在就听", "settings.description.recentAdd": "最近加入",
"settings.description.recentAdd":"最近加入", "settings.description.songs": "歌曲",
"settings.description.songs":"歌曲",
"settings.notyf.updateCider.update-not-available": "没有可用的更新", "settings.notyf.updateCider.update-not-available": "没有可用的更新",
"settings.notyf.updateCider.update-downloaded": "更新已成功下载,重启后进行更新", "settings.notyf.updateCider.update-downloaded": "更新已成功下载,重启后进行更新",
"settings.notyf.updateCider.update-timeout": "更新超时", "settings.notyf.updateCider.update-timeout": "更新超时",
"settings.header.audio": "音频", "settings.header.audio": "音频",
"settings.header.audio.description": "调整 Cider 的音频设置", "settings.header.audio.description": "调整 Cider 的音频设置",
"settings.option.audio.advanced":"高级功能", "settings.option.audio.volumeStep": "音量改变量",
"settings.option.audio.changePlaybackRate":"修改播放速度", "settings.option.audio.advanced": "高级功能",
"settings.option.audio.playbackRate":"播放速度", "settings.option.audio.maxVolume": "最大音量",
"settings.option.audio.playbackRate.change":"修改", "settings.option.audio.changePlaybackRate": "修改播放速度",
"settings.option.audio.playbackRate": "播放速度",
"settings.option.audio.playbackRate.change": "修改",
"settings.option.audio.quality": "音质", "settings.option.audio.quality": "音质",
"settings.header.audio.quality.hireslossless": "高解析度无损", "settings.header.audio.quality.hireslossless": "高解析度无损",
"settings.header.audio.quality.hireslossless.description": "(最高 24 位/192 kHz)", "settings.header.audio.quality.hireslossless.description": "(最高 24 位/192 kHz)",
"settings.header.audio.quality.lossless": "无损", "settings.header.audio.quality.lossless": "无损",
"settings.header.audio.quality.lossless.description": "(最高 24 位/48 kHz)", "settings.header.audio.quality.lossless.description": "(最高 24 位/48 kHz)",
"settings.header.audio.quality.high": "高音质", "settings.header.audio.quality.high": "高音质",
"settings.header.audio.quality.high.description":"256 kbps", "settings.header.audio.quality.high.description": "256 kbps",
"settings.header.audio.quality.standard": "高效率", "settings.header.audio.quality.standard": "高效率",
"settings.header.audio.quality.standard.description":"64 kbps", "settings.header.audio.quality.standard.description": "64 kbps",
"settings.option.audio.seamlessTransition": "无缝播放", "settings.option.audio.seamlessTransition": "无缝播放",
"settings.option.audio.enableAdvancedFunctionality": "高级音频功能", "settings.option.audio.enableAdvancedFunctionality": "高级音频功能",
"settings.option.audio.enableAdvancedFunctionality.description": "打开 AudioContext 将启用类似音量平衡和等化器的高级设置。但这并不一定适合每部电脑,可能会发生音乐卡顿。", "settings.option.audio.enableAdvancedFunctionality.description": "打开 AudioContext 将启用类似音量平衡和等化器的高级设置。但这并不一定适合每部电脑,可能会发生音乐卡顿。",
"settings.warn.audio.enableAdvancedFunctionality.lowcores":"您的电脑可能无法处理这些功能, 您确定要继续?", "settings.warn.audio.enableAdvancedFunctionality.lowcores": "您的电脑可能无法处理这些功能, 您确定要继续?",
"settings.option.audio.audioLab": "Cider 音频实验室", "settings.option.audio.audioLab": "Cider 音频实验室",
"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 音频实验室需要打开进阶音频功能才可使用。", "settings.warn.audioLab.withoutAF": "使用 Cider 音频实验室需要打开进阶音频功能才可使用。",
"settings.warn.enableAdvancedFunctionality":"此功能需要开启高级音频功能才可使用。", "settings.warn.enableAdvancedFunctionality": "此功能需要开启高级音频功能才可使用。",
"settings.option.audio.enableAdvancedFunctionality.analogWarmth": "模拟温暖", "settings.option.audio.enableAdvancedFunctionality.analogWarmth": "模拟温暖",
"settings.option.audio.enableAdvancedFunctionality.analogWarmth.description": "以 Korg Nutube 6P1 为蓝本的模拟温暖。", "settings.option.audio.enableAdvancedFunctionality.analogWarmth.description": "以 Korg Nutube 6P1 为蓝本的模拟温暖。",
"settings.option.audio.enableAdvancedFunctionality.analogWarmthIntensity": "模拟温暖强度", "settings.option.audio.enableAdvancedFunctionality.analogWarmthIntensity": "模拟温暖强度",
@ -401,18 +404,20 @@
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E68_2": "宇治抹茶奶茶", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E68_2": "宇治抹茶奶茶",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E168_1": "春毫茉莉玛琪雅朵", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.E168_1": "春毫茉莉玛琪雅朵",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z3600": "北海道奶茶", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z3600": "北海道奶茶",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500": "月光软饼干", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500A": "月光软饼干",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.BSCBM":"布蕾黑糖鲜奶", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.BSCBM": "布蕾黑糖鲜奶",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.CUDDLE":"温暖抱抱", "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500B": "樱桃克拉芙缇",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.Z8500C": "宇治抹茶麻糬",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.CUDDLE": "温暖抱抱",
"settings.option.audio.enableAdvancedFunctionality.ciderPPE": "Cider 数码增强音频处理™️", "settings.option.audio.enableAdvancedFunctionality.ciderPPE": "Cider 数码增强音频处理™️",
"settings.option.audio.enableAdvancedFunctionality.ciderPPE.description": "通过人类的听力心理学模型和 AAC 编码特色的即时算法,强化 AAC 音频的感知音频质量。", "settings.option.audio.enableAdvancedFunctionality.ciderPPE.description": "通过人类的听力心理学模型和 AAC 编码特色的即时算法,强化 AAC 音频的感知音频质量。",
"settings.warn.audio.enableAdvancedFunctionality.ciderPPE.compatibility": "数码增强音频处理与空间音频不兼容,请先停用空间音频。",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength": "数码增强音频处理设置", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength": "数码增强音频处理设置",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.description": "将更改音频处理的激进/振奋程度(增强选项有可能会引起杂讯)。", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.description": "将更改音频处理的激进/振奋程度(增强选项有可能会引起杂讯)。",
"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":"传统", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.legacy": "传统",
"settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.aggressive": "增强", "settings.option.audio.enableAdvancedFunctionality.ciderPPEStrength.aggressive": "增强",
"settings.warn.audio.enableAdvancedFunctionality.ciderPPE.compatibility":"数码增强音频处理与空间音频不兼容,请先停用空间音频。",
"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": "此功能由音频实验室管理", "settings.option.audio.enableAdvancedFunctionality.audioNormalization.disabled": "此功能由音频实验室管理",
@ -421,28 +426,25 @@
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile": "Cider 音频空间配置档", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile": "Cider 音频空间配置档",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.description": "变更音频空间的配置档,需重新启动应用程序。", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.description": "变更音频空间的配置档,需重新启动应用程序。",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.standard": "标准", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.standard": "标准",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.soundstage":"声场", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.soundstage": "声场",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.separation":"分离感", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.separation": "分离感",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.minimal":"微调", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.minimal": "微调",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.audiophile": "发烧友", "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.audiophile": "发烧友",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.diffused": "扩散",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.bplk": "安可",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.hw2k": "延长版安可",
"settings.warn.audio.enableAdvancedFunctionality.audioSpatialization.compatibility": "音频空间无法与 CAP 相容,请关闭 CAP 在进行操作。", "settings.warn.audio.enableAdvancedFunctionality.audioSpatialization.compatibility": "音频空间无法与 CAP 相容,请关闭 CAP 在进行操作。",
"settings.option.audio.dbspl.display":"显示 dB SPL(声压)", "settings.option.audio.dbspl.display": "显示 dB SPL(声压)",
"settings.option.audio.dbspl.description":"(专业用户选项) 音量滑动条显示 dB SPL 而非 dBFS.", "settings.option.audio.dbspl.description": "(专业用户选项) 音量滑动条显示 dB SPL 而非 dBFS。",
"settings.option.audio.dbfs.calibration":"0 dBFS 校正", "settings.option.audio.dbfs.calibration": "0 dBFS 校正",
"settings.option.audio.dbfs.description":"Enter the peak Z-weighted dB SPL when Cider is at 0 dBFS.", "settings.option.audio.dbfs.description": "输入当 Cider 为 0 dBFS 时的峰值 Z 加权 dB SPL。",
"settings.option.visual.uiscale": "UI界面大小",
"settings.header.visual": "外观", "settings.header.visual": "外观",
"settings.header.visual.description": "调整 Cider 的外观", "settings.header.visual.description": "调整 Cider 的外观",
"settings.option.visual.windowStyle":"窗口风格", "settings.option.visual.windowStyle": "窗口风格",
"settings.option.visual.customAccentColor": "自定义强调色",
"settings.option.visual.accentColor": "强调色",
"settings.option.visual.purplePodcastPlaybackBar": "播放播客时使用紫色进度条",
"settings.option.visual.windowColor": "窗口色调",
"settings.option.visual.windowBackgroundStyle": "窗口背景样式", "settings.option.visual.windowBackgroundStyle": "窗口背景样式",
"settings.header.visual.windowBackgroundStyle.none": "无", "settings.header.visual.windowBackgroundStyle.none": "无",
"settings.header.visual.windowBackgroundStyle.artwork": "专辑插图", "settings.header.visual.windowBackgroundStyle.artwork": "专辑插图",
"settings.header.visual.windowBackgroundStyle.image": "图像", "settings.header.visual.windowBackgroundStyle.image": "图像",
"settings.header.visual.windowBackgroundStyle.color":"色调",
"settings.option.visual.animatedArtwork": "动态专辑插图", "settings.option.visual.animatedArtwork": "动态专辑插图",
"settings.header.visual.animatedArtwork.always": "总是显示", "settings.header.visual.animatedArtwork.always": "总是显示",
"settings.header.visual.animatedArtwork.limited": "只在艺人页面和专辑插图显示", "settings.header.visual.animatedArtwork.limited": "只在艺人页面和专辑插图显示",
@ -458,20 +460,22 @@
"settings.option.visual.hardwareAcceleration.description": "需要重启 Cider 才会生效", "settings.option.visual.hardwareAcceleration.description": "需要重启 Cider 才会生效",
"settings.header.visual.hardwareAcceleration.default": "默认", "settings.header.visual.hardwareAcceleration.default": "默认",
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU", "settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
"settings.option.visual.uiscale": "UI界面大小",
"settings.header.visual.theme": "主题", "settings.header.visual.theme": "主题",
"settings.option.visual.theme.github.download": "从 GitHub 链接安装", "settings.option.visual.theme.github.download": "从 GitHub 链接安装",
"settings.option.visual.theme.github.openfolder":"开启主题文件夹", "settings.option.visual.theme.github.openfolder": "开启主题文件夹",
"settings.option.visual.theme.github.explore": "浏览 GitHub 上的主题", "settings.option.visual.theme.github.explore": "浏览 GitHub 上的主题",
"settings.header.visual.theme.github.page":"GitHub 上的主题", "settings.header.visual.theme.github.page": "GitHub 上的主题",
"settings.option.visual.theme.github.install.confirm":"你确定要安装 {{ repo }}", "settings.option.visual.theme.github.install.confirm": "你确定要安装 {{ repo }}",
"settings.prompt.visual.theme.github.URL": "输入您要安装的窗口主题链接", "settings.prompt.visual.theme.github.URL": "输入您要安装的窗口主题链接",
"settings.prompt.visual.theme.uninstallTheme":"你确定要删除 {{ theme }}", "settings.prompt.visual.theme.uninstallTheme": "你确定要删除 {{ theme }}",
"settings.option.visual.theme.checkForUpdates":"检查更新", "settings.option.visual.theme.checkForUpdates": "检查更新",
"settings.option.visual.theme.manageStyles":"管理风格", "settings.header.visual.styles": "主题",
"settings.option.visual.theme.uninstall":"卸载", "settings.option.visual.theme.manageStyles": "管理主题",
"settings.option.visual.theme.viewInfo":"查看信息", "settings.option.visual.theme.uninstall": "卸载",
"settings.option.visual.theme.github.available":"可使用的主题", "settings.option.visual.theme.viewInfo": "查看信息",
"settings.option.visual.theme.github.applied":"已应用", "settings.option.visual.theme.github.available": "可使用的主题",
"settings.option.visual.theme.github.applied": "已应用",
"settings.notyf.visual.theme.install.success": "主题成功安装", "settings.notyf.visual.theme.install.success": "主题成功安装",
"settings.notyf.visual.theme.install.error": "主题安装失败", "settings.notyf.visual.theme.install.error": "主题安装失败",
"settings.header.visual.plugin": "插件", "settings.header.visual.plugin": "插件",
@ -503,21 +507,27 @@
"settings.option.lyrics.enableQQLyrics": "启用 QQ 音乐的歌词", "settings.option.lyrics.enableQQLyrics": "启用 QQ 音乐的歌词",
"settings.header.connectivity": "外部连接", "settings.header.connectivity": "外部连接",
"settings.header.connectivity.description": "调整 Cider 与外部应用的交互设置", "settings.header.connectivity.description": "调整 Cider 与外部应用的交互设置",
"settings.option.connectivity.discordRPC": "Discord 动态",
"settings.option.connectivity.playbackNotifications": "歌曲播放通知", "settings.option.connectivity.playbackNotifications": "歌曲播放通知",
"settings.option.connectivity.discordRPC": "Discord 动态",
"settings.option.connectivity.discordRPC.clientName": "应用程序名称", "settings.option.connectivity.discordRPC.clientName": "应用程序名称",
"settings.option.connectivity.discordRPC.clearOnPause": "暂停时清除 Discord 动态", "settings.option.connectivity.discordRPC.clearOnPause": "暂停时清除 Discord 动态",
"settings.option.connectivity.discordRPC.hideButtons": "隐藏 Discord 动态上的按钮", "settings.option.connectivity.discordRPC.hideButtons": "隐藏 Discord 动态上的按钮",
"settings.option.connectivity.discordRPC.hideTimestamp":"隐藏 Discord 动态上的时间戳", "settings.option.connectivity.discordRPC.hideTimestamp": "隐藏 Discord 动态上的时间戳",
"settings.option.connectivity.discordRPC.detailsFormat": "详细信息格式", "settings.option.connectivity.discordRPC.detailsFormat": "详细信息格式",
"settings.option.connectivity.discordRPC.stateFormat": "动态格式", "settings.option.connectivity.discordRPC.stateFormat": "动态格式",
"settings.option.connectivity.discordRPC.reload":"重新加载 DiscordRPC", "settings.option.connectivity.discordRPC.reload": "重新加载 DiscordRPC",
"settings.option.connectivity.discordRPC.reconnectedToUser":"DiscordRPC 重新连接至用户: {{user}} ({{userid}})", "settings.option.connectivity.discordRPC.reconnectedToUser": "DiscordRPC 重新连接至用户: {{user}} ({{userid}})",
"settings.option.connectivity.lastfmScrobble": "Last.FM 音乐记录", "settings.option.connectivity.lastfmScrobble": "Last.FM 音乐记录",
"settings.option.connectivity.lastfmScrobble.delay": "Last.FM 歌曲追踪延迟 (%)", "settings.option.connectivity.lastfmScrobble.delay": "Last.FM 歌曲追踪延迟 (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "打开 Last.FM 正在聆听", "settings.option.connectivity.lastfmScrobble.nowPlaying": "打开 Last.FM 正在聆听",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "从歌名里去除合作者 (Last.FM)", "settings.option.connectivity.lastfmScrobble.removeFeatured": "从歌名里去除合作者 (Last.FM)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "不记录单曲循环 (Last.FM)", "settings.option.connectivity.lastfmScrobble.filterLoop": "不记录单曲循环 (Last.FM)",
"settings.option.connectivity.lastfmScrobble.filterLoop.description": "防止循环单曲被记录或展示在Last.FM 的正在播放列表中。",
"settings.option.connectivity.lastfmScrobble.filterTypes": "过滤媒体类型 (Last.fm)",
"settings.option.connectivity.lastfmScrobble.manualToken": "手动输入 Last.fm 验证码",
"settings.notyf.connectivity.lastfmScrobble.connectError": "Last.fm 连接超时",
"settings.notyf.connectivity.lastfmScrobble.connectSuccess": "Last.fm 连接成功",
"settings.notyf.connectivity.lastfmScrobble.connecting": "正在连接至 Last.fm...",
"settings.header.debug": "Debug", "settings.header.debug": "Debug",
"settings.option.debug.copy_log": "拷贝日志至剪贴板", "settings.option.debug.copy_log": "拷贝日志至剪贴板",
"settings.option.debug.openAppData": "打开 Cider 程序文件夹", "settings.option.debug.openAppData": "打开 Cider 程序文件夹",
@ -529,15 +539,21 @@
"settings.option.experimental.unknownPlugin.description": "允许从 Cider 来源以外的 repo 安装插件", "settings.option.experimental.unknownPlugin.description": "允许从 Cider 来源以外的 repo 安装插件",
"settings.option.experimental.compactUI": "紧凑型 UI", "settings.option.experimental.compactUI": "紧凑型 UI",
"settings.option.window.close_button_hide": "关闭按钮将 Cider 隐藏至系统栏", "settings.option.window.close_button_hide": "关闭按钮将 Cider 隐藏至系统栏",
"settings.option.window.maxElementScale": "最大元素比例",
"settings.option.experimental.inline_playlists": "将播放列表做为行内元素显示", "settings.option.experimental.inline_playlists": "将播放列表做为行内元素显示",
"settings.option.advanced.playlistTrackMapping": "播放列表追踪映射", "settings.option.advanced.playlistTrackMapping": "播放列表追踪映射",
"settings.option.advanced.playlistTrackMapping.description": "打开对播放列表的深度扫描,以确认歌曲在哪些播放列表中。但播放列表加载时间会显著增加。", "settings.option.advanced.playlistTrackMapping.description": "打开对播放列表的深度扫描,以确认歌曲在哪些播放列表中。但播放列表加载时间会显著增加。",
"settings.option.visual.transparent": "透明窗口框架", "settings.option.visual.transparent": "透明窗口框架",
"settings.option.visual.transparent.description": "需主题有支持透明框架,且须重新启动才会生效。", "settings.option.visual.transparent.description": "需主题有支持透明框架,且须重新启动才会生效。",
"settings.option.visual.customAccentColor": "自定义强调色",
"settings.option.visual.accentColor": "强调色",
"settings.option.visual.purplePodcastPlaybackBar": "播放播客时使用紫色进度条",
"settings.option.visual.windowColor": "窗口色调",
"settings.header.visual.windowBackgroundStyle.color": "色调",
"settings.header.advanced": "高级", "settings.header.advanced": "高级",
"settings.header.connect":"同步", "settings.header.connect": "同步",
"settings.option.connect.link_account":"开启 Cider Connect 同步", "settings.option.connect.link_account": "开启 Cider Connect 同步",
"settings.option.connect.link_account.description":"将您的 Discord 帐户与 Cider Connect 关联后,您可以储存用户资料,包括设定、均衡器,并在后续版本中加入更多可同步选项。(正在更新中)", "settings.option.connect.link_account.description": "将您的 Discord 帐户与 Cider Connect 关联后,您可以储存用户资料,包括设定、均衡器,并在后续版本中加入更多可同步选项。(正在更新中)",
"spatial.notTurnedOn": "请在设置中开启空间音频。", "spatial.notTurnedOn": "请在设置中开启空间音频。",
"spatial.spatialProperties": "空间属性", "spatial.spatialProperties": "空间属性",
"spatial.width": "宽度", "spatial.width": "宽度",
@ -560,54 +576,54 @@
"settings.header.unfinished": "未完成", "settings.header.unfinished": "未完成",
"remote.web.title": "Cider 远程控制", "remote.web.title": "Cider 远程控制",
"remote.web.description": "扫描以下的二维码以控制 Cider", "remote.web.description": "扫描以下的二维码以控制 Cider",
"share.platform.twitter.tweet": "在 Apple Music 上聆听 {{song}}。 \n\n{{url}}\n\n#AppleMusic #Cider",
"share.platform.twitter": "Twitter",
"share.platform.facebook": "Facebook",
"share.platform.reddit": "Reddit",
"share.platform.telegram": "Telegram",
"share.platform.whatsapp": "WhatsApp",
"share.platform.messenger": "Messenger",
"share.platform.email": "电子邮件",
"share.platform.songLink": "复制 song.link 链接",
"share.platform.clipboard": "复制到剪贴板",
"about.thanks": "郑重感谢 Cider Collective 以及为这个项目提供支持的贡献者。", "about.thanks": "郑重感谢 Cider Collective 以及为这个项目提供支持的贡献者。",
"share.platform.twitter.tweet":"在 Apple Music 上聆听 {{song}}。 \n\n{{url}}\n\n#AppleMusic #Cider", "oobe.yes": "好的",
"share.platform.twitter":"Twitter", "oobe.no": "不",
"share.platform.facebook":"Facebook", "oobe.next": "下一步",
"share.platform.reddit":"Reddit", "oobe.previous": "上一步",
"share.platform.telegram":"Telegram", "oobe.done": "完成",
"share.platform.whatsapp":"WhatsApp", "oobe.amupsell.title": "在我们开始之前",
"share.platform.messenger":"Messenger", "oobe.amupsell.text": "使用 Cider 需要付费的 Apple Music 订阅。\nCider 不能在 Apple Music Voice 计划或某些促销试用订阅状态下使用。 如果您已经订阅 Apple Music请点击下一步继续。",
"share.platform.email":"电子邮件", "oobe.amupsell.subscribeBtn": "订阅 Apple Music",
"share.platform.songLink":"复制 song.link 链接", "oobe.amupsell.explainBtn": "这是什么?",
"share.platform.clipboard":"复制到剪贴板", "oobe.amupsell.subscribeUrl": "https://apple.co/3MdqJVQ",
"oobe.yes":"好的", "oobe.amupsell.amWebUrl": "https://beta.music.apple.com/",
"oobe.no":"不", "oobe.amupsell.promoExplained": "Cider 无法获取部分促销活动与非美区 Apple Muisc 试用状态下的网络播放器API. 要验证您的试用订阅是否能够在Cider内使用, 点击<a href='{{ amWebUrl }}'>{{ amWebUrl }}</a>, 登陆后尝试播放音乐。如果能够播放,您就可以使用 Cider 了!否则请考虑订阅 Apple Music 服务: <a href='{{ subscribeUrl }}'>{{ subscribeUrl }}</a>。",
"oobe.next":"下一步", "oobe.intro.title": "欢迎使用 Cider",
"oobe.previous":"上一步", "oobe.intro.subtitle": "",
"oobe.done":"完成", "oobe.intro.text": "为了按您喜欢的方式使用 Cider ,请先完成一些设置。您之后可以随时改变这些设置。",
"oobe.amupsell.title":"在我们开始之前", "oobe.general.title": "通用设置",
"oobe.amupsell.text":"使用 Cider 需要付费的 Apple Music 订阅。\nCider 不能在 Apple Music Voice 计划或某些促销试用订阅状态下使用。 如果您已经订阅 Apple Music请点击下一步继续。", "oobe.general.subtitle": "",
"oobe.amupsell.subscribeBtn":"订阅 Apple Music", "oobe.general.text": "",
"oobe.amupsell.explainBtn":"这是什么?", "oobe.audio.title": "音频设置",
"oobe.amupsell.subscribeUrl":"https://apple.co/3MdqJVQ", "oobe.audio.subtitle": "",
"oobe.amupsell.amWebUrl":"https://beta.music.apple.com/", "oobe.audio.text": "Cider 能够自定义调整和设置的音频属性提供丰富的高品质音频体验包括Cider Adrenaline Processor气氛实现器和空间音频。要启用这些功能必须启用 \"高级音频功能\"。",
"oobe.amupsell.promoExplained":"Cider 无法获取部分促销活动与非美区 Apple Muisc 试用状态下的网络播放器API. 要验证您的试用订阅是否能够在Cider内使用, 点击<a href='{{ amWebUrl }}'>{{ amWebUrl }}</a>, 登陆后尝试播放音乐。如果能够播放,您就可以使用 Cider 了!否则请考虑订阅 Apple Music 服务: <a href='{{ subscribeUrl }}'>{{ subscribeUrl }}</a>。", "oobe.audio.advancedFunctionality": "",
"oobe.intro.title":"欢迎使用 Cider", "oobe.visual.title": "外观设置",
"oobe.intro.subtitle":"", "oobe.visual.subtitle": "",
"oobe.intro.text":"为了按您喜欢的方式使用 Cider ,请先完成一些设置。您之后可以随时改变这些设置。", "oobe.visual.text": "",
"oobe.general.title":"通用设置", "oobe.visual.layout.text": "Cider 拥有两种不同的窗口布局。Maverick 是一个类似 iTunes 的布局播放器在窗口的顶部。Mojave 是由 Cider 团队设计的一种新的布局。您可以在设置中随时改变布局。",
"oobe.general.subtitle":"", "oobe.visual.suggestingThemes": "主题能够个性化您的播放器。以下是推荐的几个主题:",
"oobe.general.text":"", "oobe.visual.suggestingThemes.subtext": "(主题会从 GitHub 上下载)",
"oobe.audio.title":"音频设置", "oobe.visual.suggestingThemes.default": "Cider",
"oobe.audio.subtitle":"", "oobe.visual.suggestingThemes.default.text": "经典的 Cider 主题。",
"oobe.audio.text":"Cider 能够自定义调整和设置的音频属性提供丰富的高品质音频体验包括Cider Adrenaline Processor气氛实现器和空间音频。要启用这些功能必须启用 \"高级音频功能\"。", "oobe.visual.suggestingThemes.dark": "Dark",
"oobe.audio.advancedFunctionality":"", "oobe.visual.suggestingThemes.dark.text": "暗黑模式。",
"oobe.visual.title":"外观设置", "oobe.visual.suggestingThemes.community1": "Groovy",
"oobe.visual.subtitle":"", "oobe.visual.suggestingThemes.community1.text": "类 WinUI 主题。",
"oobe.visual.text":"", "oobe.visual.suggestingThemes.community2": "iTheme",
"oobe.visual.layout.text":"Cider 拥有两种不同的窗口布局。Maverick 是一个类似 iTunes 的布局播放器在窗口的顶部。Mojave 是由 Cider 团队设计的一种新的布局。您可以在设置中随时改变布局。", "oobe.visual.suggestingThemes.community2.text": "经典的苹果风主题。",
"oobe.visual.suggestingThemes":"主题能够个性化您的播放器。以下是推荐的几个主题:", "oobe.visual.suggestingThemes.community3": "Dracula",
"oobe.visual.suggestingThemes.subtext":"(主题会从 GitHub 上下载)", "oobe.visual.suggestingThemes.community3.text": "著名的德古拉吸血鬼主题。",
"oobe.visual.suggestingThemes.default":"Cider",
"oobe.visual.suggestingThemes.default.text":"传统的 Cider 主题。",
"oobe.visual.suggestingThemes.dark":"Dark",
"oobe.visual.suggestingThemes.dark.text":"暗黑模式。",
"oobe.visual.suggestingThemes.community1":"Groovy",
"oobe.visual.suggestingThemes.community1.text":"类 WinUI 主题。",
"oobe.visual.suggestingThemes.community2":"iTheme",
"oobe.visual.suggestingThemes.community2.text":"基于 MacOS Monterey 的 Apple Music bata 主题。",
"oobe.visual.suggestingThemes.community3":"Dracula",
"oobe.visual.suggestingThemes.community3.text":"著名的德古拉吸血鬼主题。",
"oobe.amsignin.title": "" "oobe.amsignin.title": ""
} }

View file

@ -19,7 +19,10 @@
"term.accountSettings": "帳戶設定", "term.accountSettings": "帳戶設定",
"term.logout": "登出", "term.logout": "登出",
"term.login": "登入", "term.login": "登入",
"term.quit" : "結束",
"term.about": "關於", "term.about": "關於",
"term.cast" : "投影",
"term.cast2" : "投影到裝置",
"term.privateSession": "私人時間", "term.privateSession": "私人時間",
"term.queue": "待播清單", "term.queue": "待播清單",
"term.lyrics": "歌詞", "term.lyrics": "歌詞",
@ -46,6 +49,7 @@
"term.navigateBack": "回上一頁", "term.navigateBack": "回上一頁",
"term.navigateForward": "到下一頁", "term.navigateForward": "到下一頁",
"term.play": "播放", "term.play": "播放",
"term.playpause": "播放/暫停",
"term.pause": "暫停", "term.pause": "暫停",
"term.stop": "停止", "term.stop": "停止",
"term.previous": "上一首", "term.previous": "上一首",
@ -131,19 +135,25 @@
"term.videos": "音樂錄影帶", "term.videos": "音樂錄影帶",
"term.menu": "選單", "term.menu": "選單",
"term.check": "檢查", "term.check": "檢查",
"term.themeManaged": "此功能現在由主題管理。",
"term.aboutArtist": "關於{{artistName}}", "term.aboutArtist": "關於{{artistName}}",
"term.requestError": "請求發生錯誤。", "term.requestError": "請求發生錯誤。",
"term.song.link.generate": "正在取得 song.link 的分享網址...", "term.song.link.generate": "正在取得 song.link 的分享網址...",
"term.musicVideos": "音樂錄影帶",
"term.version": "版本", "term.version": "版本",
"term.creditDesignedBy": "由 ${authorUsername} 設計", "term.creditDesignedBy": "由 ${authorUsername} 設計",
"term.plugin": "模組", "term.plugin": "模組",
"term.plugins": "模組",
"term.pluginMenu": "模組選單", "term.pluginMenu": "模組選單",
"term.pluginMenu.none": "沒有交互式模組", "term.pluginMenu.none": "沒有交互式模組",
"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.gettingArtists" : "取得追蹤的藝人歌手列表... " ,
"home.madeForYou": "為您推薦", "home.madeForYou": "為您推薦",
"home.friendsListeningTo": "朋友正在聆聽", "home.friendsListeningTo": "朋友正在聆聽",
"home.followedArtists": "追蹤的藝人", "home.followedArtists": "追蹤的藝人",
@ -217,13 +227,15 @@
"action.deletepreset": "刪除預設", "action.deletepreset": "刪除預設",
"action.open": "開啟", "action.open": "開啟",
"action.cast.chromecast": "Chromecast", "action.cast.chromecast": "Chromecast",
"action.cast.todevices": "投到裝置", "action.cast.todevices": "投到裝置",
"action.cast.stop": "停止投到所有裝置", "action.cast.stop": "停止投到所有裝置",
"action.cast.airplay": "AirPlay", "action.cast.airplay": "AirPlay",
"action.cast.airplay.underdevelopment": "AirPlay 仍處於開發階段中,敬請期待。", "action.cast.airplay.underdevelopment": "AirPlay 仍處於開發階段中,敬請期待。",
"action.cast.scan": "尋找", "action.cast.scan": "尋找",
"action.cast.scanning": "尋找中...", "action.cast.scanning": "尋找中...",
"action.createNew": "新增...", "action.createNew": "新增...",
"action.refresh": "重新整理",
"menubar.options.reload": "重新載入",
"settings.header.general": "一般", "settings.header.general": "一般",
"settings.header.general.description": "調整 Cider 的一般設定", "settings.header.general.description": "調整 Cider 的一般設定",
"settings.option.general.resumebehavior": "還原行為", "settings.option.general.resumebehavior": "還原行為",
@ -256,6 +268,9 @@
"settings.header.audio.description": "調整 Cider 的音訊設定", "settings.header.audio.description": "調整 Cider 的音訊設定",
"settings.option.audio.volumeStep": "音量改變量", "settings.option.audio.volumeStep": "音量改變量",
"settings.option.audio.maxVolume": "最大音量", "settings.option.audio.maxVolume": "最大音量",
"settings.option.audio.changePlaybackRate": "更改播放速率",
"settings.option.audio.playbackRate": "播放速率",
"settings.option.audio.playbackRate.change": "更改",
"settings.option.audio.quality": "音訊品質", "settings.option.audio.quality": "音訊品質",
"settings.header.audio.quality.hireslossless": "高品質無損壓縮", "settings.header.audio.quality.hireslossless": "高品質無損壓縮",
"settings.header.audio.quality.hireslossless.description": "最高24位元/192 kHz", "settings.header.audio.quality.hireslossless.description": "最高24位元/192 kHz",
@ -304,6 +319,11 @@
"settings.option.visual.uiscale": "介面顯示大小", "settings.option.visual.uiscale": "介面顯示大小",
"settings.header.visual": "外觀", "settings.header.visual": "外觀",
"settings.header.visual.description": "調整 Cider 的外觀", "settings.header.visual.description": "調整 Cider 的外觀",
"settings.option.visual.windowStyle": "視窗布局風格",
"settings.option.visual.customAccentColor": "自訂強調色",
"settings.option.visual.accentColor": "強調色",
"settings.option.visual.purplePodcastPlaybackBar": "Podcasts 功能的紫色播放列",
"settings.option.visual.windowColor": "視窗色調顏色",
"settings.option.visual.windowBackgroundStyle": "視窗背景樣式", "settings.option.visual.windowBackgroundStyle": "視窗背景樣式",
"settings.header.visual.windowBackgroundStyle.none": "空白", "settings.header.visual.windowBackgroundStyle.none": "空白",
"settings.header.visual.windowBackgroundStyle.artwork": "專輯插圖", "settings.header.visual.windowBackgroundStyle.artwork": "專輯插圖",
@ -328,6 +348,8 @@
"settings.option.visual.theme.github.openfolder": "開啟主題存放位置", "settings.option.visual.theme.github.openfolder": "開啟主題存放位置",
"settings.option.visual.theme.github.explore": "探索 GitHub 上的主題", "settings.option.visual.theme.github.explore": "探索 GitHub 上的主題",
"settings.prompt.visual.theme.github.URL": "輸入你要安裝的主題網址", "settings.prompt.visual.theme.github.URL": "輸入你要安裝的主題網址",
"settings.option.visual.theme.checkForUpdates": "檢查更新",
"settings.header.visual.styles": "主題",
"settings.option.visual.theme.manageStyles": "管理主題", "settings.option.visual.theme.manageStyles": "管理主題",
"settings.option.visual.theme.uninstall": "移除", "settings.option.visual.theme.uninstall": "移除",
"settings.option.visual.theme.viewInfo": "查看資訊", "settings.option.visual.theme.viewInfo": "查看資訊",
@ -349,6 +371,7 @@
"settings.option.visual.showPersonalInfo": "顯示個人檔案", "settings.option.visual.showPersonalInfo": "顯示個人檔案",
"settings.header.window": "視窗", "settings.header.window": "視窗",
"settings.header.window.description": "調整 Cider 的視窗設定", "settings.header.window.description": "調整 Cider 的視窗設定",
"settings.option.window.maxElementScale": "最大元素比例",
"settings.option.window.openOnStartup": "開機時,啟動 Cider ", "settings.option.window.openOnStartup": "開機時,啟動 Cider ",
"settings.option.window.openOnStartup.hidden": "啟動時,自動隱藏至系統列", "settings.option.window.openOnStartup.hidden": "啟動時,自動隱藏至系統列",
"settings.option.window.useNativeTitleBar": "使用原生視窗標題列", "settings.option.window.useNativeTitleBar": "使用原生視窗標題列",
@ -362,7 +385,7 @@
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌詞優先語言偏好選項", "settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌詞優先語言偏好選項",
"settings.option.lyrics.enableYoutubeLyrics": "播放 MV 時,使用 YouTube 歌詞", "settings.option.lyrics.enableYoutubeLyrics": "播放 MV 時,使用 YouTube 歌詞",
"settings.option.lyrics.enableQQLyrics": "開啟 QQ 音樂的歌詞", "settings.option.lyrics.enableQQLyrics": "開啟 QQ 音樂的歌詞",
"settings.header.connectivity": "外部連接", "settings.header.connectivity": "連接",
"settings.header.connectivity.description": "調整 Cider 與外部的連接", "settings.header.connectivity.description": "調整 Cider 與外部的連接",
"settings.option.connectivity.playbackNotifications": "歌曲播放通知", "settings.option.connectivity.playbackNotifications": "歌曲播放通知",
"settings.option.connectivity.discordRPC": "Discord 動態", "settings.option.connectivity.discordRPC": "Discord 動態",
@ -379,6 +402,12 @@
"settings.option.connectivity.lastfmScrobble.nowPlaying": "開啟 Last.FM 正在聆聽", "settings.option.connectivity.lastfmScrobble.nowPlaying": "開啟 Last.FM 正在聆聽",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "從 Last.FM 的歌名中移除客串藝人", "settings.option.connectivity.lastfmScrobble.removeFeatured": "從 Last.FM 的歌名中移除客串藝人",
"settings.option.connectivity.lastfmScrobble.filterLoop": "讓 Last.FM 不記錄單曲循環", "settings.option.connectivity.lastfmScrobble.filterLoop": "讓 Last.FM 不記錄單曲循環",
"settings.option.connectivity.lastfmScrobble.filterLoop.description": "防止循環單曲被打亂或顯示在 Last.FM 的正在播放列表中。",
"settings.option.connectivity.lastfmScrobble.filterTypes": "過濾媒體類型 (Last.FM)",
"settings.option.connectivity.lastfmScrobble.manualToken": "手動輸入 Last.FM 驗證碼",
"settings.notyf.connectivity.lastfmScrobble.connectError": "Last.FM 連線超時",
"settings.notyf.connectivity.lastfmScrobble.connectSuccess": "Last.FM 連線成功",
"settings.notyf.connectivity.lastfmScrobble.connecting": "正在連線到 Last.FM...",
"settings.header.debug": "除錯", "settings.header.debug": "除錯",
"settings.option.debug.copy_log": "複製執行紀錄檔至剪貼簿", "settings.option.debug.copy_log": "複製執行紀錄檔至剪貼簿",
"settings.option.debug.openAppData": "打開 Cider 資料夾", "settings.option.debug.openAppData": "打開 Cider 資料夾",

View file

@ -162,13 +162,10 @@ export class AppEvents {
// LastFM Auth URL // LastFM Auth URL
if (arg.includes('auth')) { if (arg.includes('auth')) {
let authURI = arg.split('/auth/')[1] const authURI = arg.split('/auth/')[1]
if (authURI.startsWith('lastfm')) { // If we wanted more auth options if (authURI.startsWith('lastfm')) { // If we wanted more auth options
const authKey = authURI.split('lastfm?token=')[1]; console.log('token: ', authURI.split('lastfm?token=')[1])
utils.setStoreValue('lastfm.enabled', true); utils.getWindow().webContents.executeJavaScript(`ipcRenderer.send('lastfm:auth', "${authURI.split('lastfm?token=')[1]}")`).catch(console.error)
utils.setStoreValue('lastfm.auth_token', authKey);
utils.getWindow().webContents.send('LastfmAuthenticated', authKey);
this.plugin.callPlugin('lastfm', 'authenticate', authKey);
} }
} }
// Play // Play

View file

@ -1,9 +1,9 @@
import {join} from "path"; import { join } from "path";
import {app, BrowserWindow as bw, ipcMain, ShareMenu, shell, screen} from "electron"; import { app, BrowserWindow as bw, ipcMain, ShareMenu, shell, screen, dialog } from "electron";
import * as windowStateKeeper from "electron-window-state"; import * as windowStateKeeper from "electron-window-state";
import * as express from "express"; import * as express from "express";
import * as getPort from "get-port"; import * as getPort from "get-port";
import {search} from "youtube-search-without-api-key"; import { search } from "youtube-search-without-api-key";
import { import {
existsSync, existsSync,
rmSync, rmSync,
@ -16,19 +16,18 @@ import {
rmdirSync, rmdirSync,
lstatSync, lstatSync,
} from "fs"; } from "fs";
import {Stream} from "stream"; import { Stream } from "stream";
import {networkInterfaces} from "os"; import { networkInterfaces } from "os";
import * as mm from 'music-metadata'; import * as mm from 'music-metadata';
import fetch from 'electron-fetch' import fetch from 'electron-fetch'
import {wsapi} from "./wsapi"; import { wsapi } from "./wsapi";
import {utils} from './utils'; import { utils } from './utils';
import {Plugins} from "./plugins"; import { Plugins } from "./plugins";
import {watch} from "chokidar"; import { watch } from "chokidar";
import * as os from "os"; import * as os from "os";
import wallpaper from "wallpaper"; import wallpaper from "wallpaper";
import * as AdmZip from "adm-zip"; import * as AdmZip from "adm-zip";
import * as path from 'path'; import { LocalFiles } from "../providers/local/";
const { readdir } = require('fs').promises;
/** /**
@ -40,11 +39,11 @@ const { readdir } = require('fs').promises;
export class BrowserWindow { export class BrowserWindow {
public static win: any | undefined = null; public static win: any | undefined = null;
private devMode: boolean = !app.isPackaged; private devMode: boolean = !app.isPackaged;
public static express: any | undefined = null;
private audioStream: any = new Stream.PassThrough(); private audioStream: any = new Stream.PassThrough();
private headerSent: any = false; private headerSent: any = false;
private chromecastIP: any = []; private chromecastIP: any = [];
private localSongs: any = [];
private clientPort: number = 0; private clientPort: number = 0;
private remotePort: number = 6942; private remotePort: number = 6942;
private EnvironmentVariables: object = { private EnvironmentVariables: object = {
@ -64,8 +63,7 @@ export class BrowserWindow {
"pages/browse", "pages/browse",
"pages/groupings", "pages/groupings",
"pages/charts", "pages/charts",
"pages/settings", //"pages/installed-themes",
"pages/installed-themes",
"pages/listen_now", "pages/listen_now",
"pages/radio", "pages/radio",
"pages/home", "pages/home",
@ -81,14 +79,16 @@ export class BrowserWindow {
"pages/about", "pages/about",
"pages/library-videos", "pages/library-videos",
"pages/remote-pair", "pages/remote-pair",
"pages/themes-github", //"pages/themes-github",
"pages/plugins-github", //"pages/plugins-github",
"pages/replay", "pages/replay",
"pages/audiolabs", "pages/audiolabs",
"pages/zoo", "pages/zoo",
"pages/plugin-renderer", "pages/plugin-renderer",
"pages/keybinds",
"pages/oobe", "pages/oobe",
"pages/cider-profile",
"components/app-content",
"components/sidebar",
"components/mediaitem-artwork", "components/mediaitem-artwork",
"components/artwork-material", "components/artwork-material",
"components/menu-panel", "components/menu-panel",
@ -119,159 +119,173 @@ export class BrowserWindow {
"components/fullscreen", "components/fullscreen",
"components/miniplayer", "components/miniplayer",
"components/castmenu", "components/castmenu",
"components/pathmenu",
"components/airplay-modal", "components/airplay-modal",
"components/artist-chip", "components/artist-chip",
"components/hello-world", "components/hello-world",
"components/inline-collection-list", "components/inline-collection-list",
"components/settings-window",
"components/settings-keybinds",
"components/settings-themes",
"components/settings-themes-github",
"components/settings-plugins-github",
], ],
appRoutes: [ appRoutes: [
{ {
page: "library-recentlyadded", page: "library-recentlyadded",
component: `<cider-recentlyadded></cider-recentlyadded>`, component: `<cider-recentlyadded></cider-recentlyadded>`,
condition: "page == 'library-recentlyadded'" condition: "$root.page == 'library-recentlyadded'"
}, },
{ {
page: "plugin-renderer", page: "plugin-renderer",
component: `<plugin-renderer></plugin-renderer>`, component: `<plugin-renderer></plugin-renderer>`,
condition: "page == 'plugin-renderer'" condition: "$root.page == 'plugin-renderer'"
}, },
{ {
page: "zoo", page: "zoo",
component: "<cider-zoo></cider-zoo>", component: "<cider-zoo></cider-zoo>",
condition: "page == 'zoo'" condition: "$root.page == 'zoo'"
}, },
{ {
page: "podcasts", page: "podcasts",
component: `<apple-podcasts></apple-podcasts>`, component: `<apple-podcasts></apple-podcasts>`,
condition: `page == 'podcasts'` condition: `$root.page == 'podcasts'`
}, { }, {
page: "library-videos", page: "library-videos",
component: `<cider-library-videos></cider-library-videos>`, component: `<cider-library-videos></cider-library-videos>`,
condition: `page == 'library-videos'` condition: `$root.page == 'library-videos'`
}, { }, {
page: "apple-account-settings", page: "apple-account-settings",
component: `<apple-account-settings></apple-account-settings>`, component: `<apple-account-settings></apple-account-settings>`,
condition: `page == 'apple-account-settings'` condition: `$root.page == 'apple-account-settings'`
}, { }, {
page: "about", page: "about",
component: `<about-page></about-page>`, component: `<about-page></about-page>`,
condition: `page == 'about'` condition: `$root.page == 'about'`
}, { }, {
page: "cider-artist", page: "cider-artist",
component: `<cider-artist :data="artistPage.data"></cider-artist>`, component: `<cider-artist :data="$root.artistPage.data"></cider-artist>`,
condition: `page == 'artist-page' && artistPage.data.attributes` condition: `$root.page == 'artist-page' && $root.artistPage.data.attributes`
}, { }, {
page: "collection-list", page: "collection-list",
component: `<cider-collection-list :data="collectionList.response" :type="collectionList.type" :title="collectionList.title"></cider-collection-list>`, component: `<cider-collection-list :data="$root.collectionList.response" :type="$root.collectionList.type" :title="$root.collectionList.title"></cider-collection-list>`,
condition: `page == 'collection-list'` condition: `$root.page == 'collection-list'`
}, { }, {
page: "home", page: "home",
component: `<cider-home></cider-home>`, component: `<cider-home></cider-home>`,
condition: `page == 'home'` condition: `$root.page == 'home'`
}, { }, {
page: "artist-feed", page: "artist-feed",
component: `<cider-artist-feed></cider-artist-feed>`, component: `<cider-artist-feed></cider-artist-feed>`,
condition: `page == 'artist-feed'` condition: `$root.page == 'artist-feed'`
}, { }, {
page: "playlist-inline", page: "playlist-inline",
component: `<playlist-inline :data="showingPlaylist"></playlist-inline>`, component: `<playlist-inline :data="$root.showingPlaylist"></playlist-inline>`,
condition: `modals.showPlaylist` condition: `$root.modals.showPlaylist`
}, { }, {
page: "playlist_", page: "playlist_",
component: `<cider-playlist :data="showingPlaylist"></cider-playlist>`, component: `<cider-playlist :data="$root.showingPlaylist"></cider-playlist>`,
condition: `page.includes('playlist_')` condition: `$root.page.includes('playlist_')`
}, { }, {
page: "album_", page: "album_",
component: `<cider-playlist :data="showingPlaylist"></cider-playlist>`, component: `<cider-playlist :data="$root.showingPlaylist"></cider-playlist>`,
condition: `page.includes('album_')` condition: `$root.page.includes('album_')`
}, { }, {
page: "recordLabel_", page: "recordLabel_",
component: `<cider-recordlabel :data="showingPlaylist"></cider-recordlabel>`, component: `<cider-recordlabel :data="$root.showingPlaylist"></cider-recordlabel>`,
condition: `page.includes('recordLabel_')` condition: `$root.page.includes('recordLabel_')`
}, {
page: "social-profiles_",
component: `<cider-socialprofile :data="$root.showingPlaylist"></cider-socialprofile>`,
condition: `$root.page.includes('social-profiles_')`
}, { }, {
page: "multiroom", page: "multiroom",
component: `<cider-multiroom :data="multiroom"></cider-multiroom>`, component: `<cider-multiroom :data="$root.multiroom"></cider-multiroom>`,
condition: `page.includes('multiroom')` condition: `$root.page.includes('multiroom')`
}, { }, {
page: "curator_", page: "curator_",
component: `<cider-recordlabel :data="showingPlaylist"></cider-recordlabel>`, component: `<cider-recordlabel :data="$root.showingPlaylist"></cider-recordlabel>`,
condition: `page.includes('curator_')` condition: `$root.page.includes('curator_')`
}, { }, {
page: "browsepage", page: "browsepage",
component: `<cider-browse :data="browsepage"></cider-browse>`, component: `<cider-browse :data="$root.browsepage"></cider-browse>`,
condition: `page == 'browse'`, condition: `$root.page == 'browse'`,
onEnter: `` onEnter: ``
},{ }, {
page: "groupings", page: "groupings",
component: `<cider-groupings :data="browsepage"></cider-groupings>`, component: `<cider-groupings :data="$root.browsepage"></cider-groupings>`,
condition: `page == 'groupings'`, condition: `$root.page == 'groupings'`,
onEnter: `` onEnter: ``
},{ }, {
page: "charts", page: "charts",
component: `<cider-charts :data="browsepage"></cider-charts>`, component: `<cider-charts :data="$root.browsepage"></cider-charts>`,
condition: `page == 'charts'`, condition: `$root.page == 'charts'`,
onEnter: `` onEnter: ``
}, { }, {
page: "listen_now", page: "listen_now",
component: `<cider-listen-now :data="listennow"></cider-listen-now>`, component: `<cider-listen-now :data="$root.listennow"></cider-listen-now>`,
condition: `page == 'listen_now'`, condition: `$root.page == 'listen_now'`,
onEnter: `` onEnter: ``
}, { }, {
page: "radio", page: "radio",
component: `<cider-radio :data="radio"></cider-radio>`, component: `<cider-radio :data="$root.radio"></cider-radio>`,
condition: `page == 'radio'`, condition: `$root.page == 'radio'`,
onEnter: `` onEnter: ``
}, { }, {
page: "settings", page: "settings",
component: `<cider-settings></cider-settings>`, component: `<cider-settings></cider-settings>`,
condition: `page == 'settings'` condition: `$root.page == 'settings'`
}, { }, {
page: "installed-themes", page: "installed-themes",
component: `<installed-themes></installed-themes>`, component: `<installed-themes></installed-themes>`,
condition: `page == 'installed-themes'` condition: `$root.page == 'installed-themes'`
}, { }, {
page: "search", page: "search",
component: `<cider-search :search="search"></cider-search>`, component: `<cider-search :search="$root.search"></cider-search>`,
condition: `page == 'search'` condition: `$root.page == 'search'`
}, { }, {
page: "library-songs", page: "library-songs",
component: `<cider-library-songs :data="library.songs"></cider-library-songs>`, component: `<cider-library-songs :data="$root.library.songs"></cider-library-songs>`,
condition: `page == 'library-songs'`, condition: `$root.page == 'library-songs'`,
onEnter: `` onEnter: ``
}, { }, {
page: "library-albums", page: "library-albums",
component: `<cider-library-albums :data="library.songs"></cider-library-albums>`, component: `<cider-library-albums :data="$root.library.songs"></cider-library-albums>`,
condition: `page == 'library-albums'`, condition: `$root.page == 'library-albums'`,
onEnter: `` onEnter: ``
}, { }, {
page: "library-artists", page: "library-artists",
component: `<cider-library-artists></cider-library-artists>`, component: `<cider-library-artists></cider-library-artists>`,
condition: `page == 'library-artists'`, condition: `$root.page == 'library-artists'`,
onEnter: `` onEnter: ``
}, { }, {
page: "appleCurator", page: "appleCurator",
component: `<cider-applecurator :data="appleCurator"></cider-applecurator>`, component: `<cider-applecurator :data="$root.appleCurator"></cider-applecurator>`,
condition: `page.includes('appleCurator')` condition: `$root.page.includes('appleCurator')`
}, { }, {
page: "themes-github", page: "themes-github",
component: `<themes-github></themes-github>`, component: `<themes-github></themes-github>`,
condition: `page == 'themes-github'` condition: `$root.page == 'themes-github'`
}, { }, {
page: "plugins-github", page: "plugins-github",
component: `<plugins-github></plugins-github>`, component: `<plugins-github></plugins-github>`,
condition: `page == 'plugins-github'` condition: `$root.page == 'plugins-github'`
}, { }, {
page: "remote-pair", page: "remote-pair",
component: `<remote-pair></remote-pair>`, component: `<remote-pair></remote-pair>`,
condition: `page == 'remote-pair'` condition: `$root.page == 'remote-pair'`
}, { }, {
page: "audiolabs", page: "audiolabs",
component: `<audiolabs-page></audiolabs-page>`, component: `<audiolabs-page></audiolabs-page>`,
condition: `page == 'audiolabs'` condition: `$root.page == 'audiolabs'`
}, { }, {
page: "replay", page: "replay",
component: `<replay-page></replay-page>`, component: `<replay-page></replay-page>`,
condition: `page == 'replay'` condition: `$root.page == 'replay'`
}, {
page: "keydinds",
component: `<keybinds-settings></keybinds-settings>`,
condition: `$root.page == 'keybinds-settings'`
} }
] ]
}, },
@ -292,7 +306,7 @@ export class BrowserWindow {
show: false, show: false,
// backgroundColor: "#1E1E1E", // backgroundColor: "#1E1E1E",
titleBarStyle: 'hidden', titleBarStyle: 'hidden',
trafficLightPosition: {x: 15, y: 20}, trafficLightPosition: { x: 15, y: 20 },
webPreferences: { webPreferences: {
experimentalFeatures: true, experimentalFeatures: true,
nodeIntegration: true, nodeIntegration: true,
@ -358,7 +372,8 @@ export class BrowserWindow {
* @yields {object} Electron browser window * @yields {object} Electron browser window
*/ */
async createWindow(): Promise<Electron.BrowserWindow> { async createWindow(): Promise<Electron.BrowserWindow> {
this.clientPort = await getPort({port: 9000}); const envPort = process.env?.CIDER_PORT || '9000'
this.clientPort = await getPort({ port: parseInt(envPort, 10) || 9000 });
BrowserWindow.verifyFiles(); BrowserWindow.verifyFiles();
this.StartWatcher(utils.getPath('themes')); this.StartWatcher(utils.getPath('themes'));
@ -405,9 +420,10 @@ export class BrowserWindow {
} }
// Start the webserver for the browser window to load // Start the webserver for the browser window to load
// LocalFiles.DB.init()
this.startWebServer(); this.startWebServer();
BrowserWindow.win = new bw(this.options); BrowserWindow.win = new bw(this.options);
// cant be built in CI // cant be built in CI
// if (process.platform === "win32" && (utils.getStoreValue('visual.transparent') ?? false)) { // if (process.platform === "win32" && (utils.getStoreValue('visual.transparent') ?? false)) {
@ -463,7 +479,7 @@ export class BrowserWindow {
*/ */
private startWebServer(): void { private startWebServer(): void {
const app = express(); const app = express();
BrowserWindow.express = app;
app.use(express.static(join(utils.getPath('srcPath'), "./renderer/"))); app.use(express.static(join(utils.getPath('srcPath'), "./renderer/")));
app.set("views", join(utils.getPath('srcPath'), "./renderer/views")); app.set("views", join(utils.getPath('srcPath'), "./renderer/views"));
app.set("view engine", "ejs"); app.set("view engine", "ejs");
@ -495,9 +511,9 @@ export class BrowserWindow {
app.get("/cideraudio/impulses/:file", (req, res) => { app.get("/cideraudio/impulses/:file", (req, res) => {
const impulseExternals = join(utils.getPath("externals"), "/impulses/") const impulseExternals = join(utils.getPath("externals"), "/impulses/")
const impulseFile = join(impulseExternals, req.params.file) const impulseFile = join(impulseExternals, req.params.file)
if(existsSync(impulseFile)) { if (existsSync(impulseFile)) {
res.sendFile(impulseFile) res.sendFile(impulseFile)
}else{ } else {
res.sendFile(join(utils.getPath('srcPath'), "./renderer/audio/impulses/" + req.params.file)) res.sendFile(join(utils.getPath('srcPath'), "./renderer/audio/impulses/" + req.params.file))
} }
}) })
@ -547,14 +563,6 @@ export class BrowserWindow {
res.send(`// Theme not found - ${userThemePath}`); res.send(`// Theme not found - ${userThemePath}`);
} }
}); });
app.get("/ciderlocal/:songs", (req, res) => {
const audio = atob(req.params.songs.replace(/_/g, '/').replace(/-/g, '+'));
console.log('auss', audio)
let data = {data:
this.localSongs.filter((f: any) => audio.split(',').includes(f.id))};
res.send(data);
});
app.get("/themes/:theme/*", (req: { params: { theme: string, 0: string } }, res) => { app.get("/themes/:theme/*", (req: { params: { theme: string, 0: string } }, res) => {
const theme = req.params.theme; const theme = req.params.theme;
@ -615,10 +623,12 @@ export class BrowserWindow {
//region Connect Integration //region Connect Integration
app.get("/connect/set-cc-user/:data", (req, res) => { app.get("/connect/set-cc-user/:data", (req, res) => {
//utils.getStoreValue('connectUser', JSON.parse()) // [Connect] Save user in store //utils.getStoreValue('connectUser', JSON.parse()) // [Connect] Save user in store
utils.setStoreValue('connectUser', JSON.parse(req.params.data)) utils.getWindow().webContents.send('setStoreValue', 'connectUser', JSON.parse(req.params.data))
utils.getWindow().reload()
res.redirect(`https://connect.cidercollective.dev/linked.html`) res.redirect(`https://connect.cidercollective.dev/linked.html`)
}); });
LocalFiles.setupHandlers()
// [Connect] Set auth URL in store for `shell.openExternal` // [Connect] Set auth URL in store for `shell.openExternal`
utils.setStoreValue('cc_authURL', `https://connect.cidercollective.dev/callback/discord?app=cider&appPort=${this.clientPort}`) utils.setStoreValue('cc_authURL', `https://connect.cidercollective.dev/callback/discord?app=cider&appPort=${this.clientPort}`)
console.log(`[Connect] Auth URL: ${utils.getStoreValue('cc_authURL')}`) console.log(`[Connect] Auth URL: ${utils.getStoreValue('cc_authURL')}`)
@ -638,7 +648,7 @@ export class BrowserWindow {
remote.use(express.static(join(utils.getPath('srcPath'), "./web-remote/"))) remote.use(express.static(join(utils.getPath('srcPath'), "./web-remote/")))
remote.set("views", join(utils.getPath('srcPath'), "./web-remote/views")); remote.set("views", join(utils.getPath('srcPath'), "./web-remote/views"));
remote.set("view engine", "ejs"); remote.set("view engine", "ejs");
getPort({port: 6942}).then((port: number) => { getPort({ port: 6942 }).then((port: number) => {
this.remotePort = port; this.remotePort = port;
// Start Remote Discovery // Start Remote Discovery
this.broadcastRemote() this.broadcastRemote()
@ -669,13 +679,13 @@ export class BrowserWindow {
callback({ callback({
redirectURL: `http://localhost:${this.clientPort}/apple-hls.js`, redirectURL: `http://localhost:${this.clientPort}/apple-hls.js`,
}); });
} else if (details.url.includes("ciderlocal")) { } else if (details.url.includes("ciderlocal") && !details.url.includes("https://apic-desktop.musixmatch.com") ) {
let text = details.url.toString().includes('ids=') ? decodeURIComponent(details.url.toString()).split("?ids=")[1] : decodeURIComponent(details.url.toString().substring(details.url.toString().lastIndexOf('/') + 1)); let text = details.url.toString().includes('ids=') ? decodeURIComponent(details.url.toString()).split("?ids=")[1] : decodeURIComponent(details.url.toString().substring(details.url.toString().lastIndexOf('/') + 1));
console.log('localurl',text) //console.log('localurl',text)
callback({ callback({
redirectURL: `http://localhost:${this.clientPort}/ciderlocal/${Buffer.from(text).toString('base64url')}`, redirectURL: `http://localhost:${this.clientPort}/ciderlocal/${Buffer.from(text).toString('base64url')}`,
}); });
}else { } else {
callback({ callback({
cancel: false, cancel: false,
}); });
@ -717,7 +727,7 @@ export class BrowserWindow {
'KHTML, like Gecko) Mobile/17D50 UCBrowser/12.8.2.1268 Mobile AliApp(TUnionSDK/0.1.20.3) ' 'KHTML, like Gecko) Mobile/17D50 UCBrowser/12.8.2.1268 Mobile AliApp(TUnionSDK/0.1.20.3) '
details.requestHeaders['Referer'] = "https://y.qq.com/portal/player.html" details.requestHeaders['Referer'] = "https://y.qq.com/portal/player.html"
} }
callback({requestHeaders: details.requestHeaders}); callback({ requestHeaders: details.requestHeaders });
} }
); );
@ -774,7 +784,7 @@ export class BrowserWindow {
const Jimp = require("jimp") const Jimp = require("jimp")
const img = await Jimp.read(wpPath) const img = await Jimp.read(wpPath)
const blurAmount = args.blurAmount ?? 256 const blurAmount = args.blurAmount ?? 256
if(blurAmount) { if (blurAmount) {
img.blur(blurAmount) img.blur(blurAmount)
} }
const screens = await screen.getAllDisplays() const screens = await screen.getAllDisplays()
@ -811,7 +821,7 @@ export class BrowserWindow {
} }
// if path is directory, delete it // if path is directory, delete it
if (lstatSync(path).isDirectory()) { if (lstatSync(path).isDirectory()) {
await rmdirSync(path, {recursive: true}); await rmdirSync(path, { recursive: true });
} else { } else {
// if path is file, delete it // if path is file, delete it
await unlinkSync(path); await unlinkSync(path);
@ -842,7 +852,7 @@ export class BrowserWindow {
// remove WidevineCDM from appdata folder // remove WidevineCDM from appdata folder
const widevineCdmPath = join(app.getPath("userData"), "./WidevineCdm"); const widevineCdmPath = join(app.getPath("userData"), "./WidevineCdm");
if (existsSync(widevineCdmPath)) { if (existsSync(widevineCdmPath)) {
rmSync(widevineCdmPath, {recursive: true, force: true}) rmSync(widevineCdmPath, { recursive: true, force: true })
} }
// reinstall WidevineCDM // reinstall WidevineCDM
app.relaunch() app.relaunch()
@ -1134,7 +1144,7 @@ export class BrowserWindow {
// Move window // Move window
ipcMain.on("windowmove", (_event, x, y) => { ipcMain.on("windowmove", (_event, x, y) => {
BrowserWindow.win.setBounds({x, y}); BrowserWindow.win.setBounds({ x, y });
}); });
//Fullscreen //Fullscreen
@ -1149,7 +1159,7 @@ export class BrowserWindow {
//Fullscreen //Fullscreen
ipcMain.on('detachDT', (_event, _) => { ipcMain.on('detachDT', (_event, _) => {
BrowserWindow.win.webContents.openDevTools({mode: 'detach'}); BrowserWindow.win.webContents.openDevTools({ mode: 'detach' });
}) })
ipcMain.handle('relaunchApp', (_event, _) => { ipcMain.handle('relaunchApp', (_event, _) => {
@ -1168,6 +1178,10 @@ export class BrowserWindow {
app.quit(); app.quit();
}) })
ipcMain.handle("quit-app", (_event, _) => {
app.quit();
})
app.on('before-quit', () => { app.on('before-quit', () => {
}) })
@ -1182,102 +1196,17 @@ export class BrowserWindow {
}); });
ipcMain.on("scanLibrary", async (event, folders) => { ipcMain.handle("scanLibrary", async (event, folders) => {
async function getFiles(dir : any) { const oldmetadatalist = await LocalFiles.sendOldLibrary()
const dirents = await readdir(dir, { withFileTypes: true }); BrowserWindow.win.webContents.send('getUpdatedLocalList', oldmetadatalist);
const files = await Promise.all(dirents.map((dirent: any) => { const metadatalist = await LocalFiles.scanLibrary()
const res = path.resolve(dir, dirent.name);
return dirent.isDirectory() ? getFiles(res) : res;
}));
return Array.prototype.concat(...files);
}
if (folders == null || folders.length == null || folders.length == 0) folders = ["D:\\Music"]
console.log('folders', folders)
let files: any[] = []
for (var folder of folders){
// get files from the Music folder
files = files.concat(await getFiles(folder))
}
//console.log("cider.files", files2);
let supporttedformats = ["mp3", "aac", "webm", "flac", "m4a", "ogg", "wav", "opus"]
let audiofiles = files.filter(f => supporttedformats.includes(f.substring(f.lastIndexOf('.') + 1)));
// console.log("cider.files2", audiofiles, audiofiles.length);
let metadatalist = []
let numid = 0;
for (var audio of audiofiles) {
try{
const metadata = await mm.parseFile(audio);
if (metadata != null){
let form = {
"id": "ciderlocal" + numid,
"type": "podcast-episodes",
"href": audio,
"attributes": {
"artwork": {
"width": 3000,
"height": 3000,
"url": metadata.common.picture != undefined ? "data:image/png;base64,"+metadata.common.picture[0].data.toString('base64')+"" : "",
},
"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": "2022-05-13T00:23:00Z",
"durationInMilliseconds": Math.floor((metadata.format.duration?? 0) * 1000),
"offers": [
{
"kind": "get",
"type": "STDQ"
}
],
"contentRating": "clean"
}
};
numid += 1;
// let form = {"id": "/ciderlocal?" + audio,
// "type": "library-songs",
// "href": "/ciderlocal?" + audio,
// "artwork": {
// "url": metadata.common.picture != undefined ? "data:image/png;base64,"+metadata.common.picture[0].data.toString('base64')+"" : "",
// },
// "attributes":
// { "durationInMillis": Math.floor((metadata.format.duration?? 0) * 1000),
// "hasLyrics": false,
// "playParams": { "id": "/ciderlocal?" + audio, "kind": "song", "isLibrary": true, "reporting": false },
// "trackNumber": 0,
// "discNumber": 0,
// "genreNames": [""],
// "name": metadata.common.title,
// "albumName": metadata.common.album,
// "artistName": metadata.common.artist}}
metadatalist.push(form)}
} catch (e){}
}
// console.log('metadatalist', metadatalist);
this.localSongs = metadatalist;
BrowserWindow.win.webContents.send('getUpdatedLocalList', metadatalist); BrowserWindow.win.webContents.send('getUpdatedLocalList', metadatalist);
} LocalFiles.cleanUpDB()
})
) LocalFiles.eventEmitter.on('newtracks', (data) => {
BrowserWindow.win.webContents.send('getUpdatedLocalList', data);
});
ipcMain.on('writeWAV', (event, leftpcm, rightpcm, bufferlength) => { ipcMain.on('writeWAV', (event, leftpcm, rightpcm, bufferlength) => {
@ -1451,7 +1380,7 @@ export class BrowserWindow {
ipcMain.on('share-menu', async (_event, url) => { ipcMain.on('share-menu', async (_event, url) => {
if (process.platform != 'darwin') return; if (process.platform !== 'darwin') return;
//https://www.electronjs.org/docs/latest/api/share-menu //https://www.electronjs.org/docs/latest/api/share-menu
console.log('[Share Sheet - App.ts]', url) console.log('[Share Sheet - App.ts]', url)
const options = { const options = {
@ -1470,10 +1399,17 @@ export class BrowserWindow {
} }
}); });
ipcMain.on('open-appdata', (_event) => { ipcMain.on('open-appdata', (_event) => {
shell.openPath(app.getPath('userData')); shell.openPath(app.getPath('userData'));
}); });
ipcMain.handle('folderSelector', async (_event) => {
let u = await dialog.showOpenDialog({
properties: ['openDirectory', 'multiSelections']
});
return u.filePaths
});
//#region Cider Connect //#region Cider Connect
ipcMain.on('cc-auth', (_event) => { ipcMain.on('cc-auth', (_event) => {
@ -1498,7 +1434,6 @@ export class BrowserWindow {
/* ********************************************************************************************* /* *********************************************************************************************
* Window Events * Window Events
* **********************************************************************************************/ * **********************************************************************************************/
if (process.platform === "win32") {
let WND_STATE = { let WND_STATE = {
MINIMIZED: 0, MINIMIZED: 0,
NORMAL: 1, NORMAL: 1,
@ -1514,19 +1449,23 @@ export class BrowserWindow {
const state = wndState; const state = wndState;
if (isMinimized && state !== WND_STATE.MINIMIZED) { if (isMinimized && state !== WND_STATE.MINIMIZED) {
wndState = WND_STATE.MINIMIZED; wndState = WND_STATE.MINIMIZED;
BrowserWindow.win.webContents.send('window-state-changed', 'minimized');
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) { } else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
wndState = WND_STATE.FULL_SCREEN; wndState = WND_STATE.FULL_SCREEN;
BrowserWindow.win.webContents.send('window-state-changed', 'fullscreen')
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) { } else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
wndState = WND_STATE.MAXIMIZED; wndState = WND_STATE.MAXIMIZED;
BrowserWindow.win.webContents.send('window-state-changed', 'maximized')
BrowserWindow.win.webContents.executeJavaScript(`app.chrome.maximized = true`); BrowserWindow.win.webContents.executeJavaScript(`app.chrome.maximized = true`);
} else if (state !== WND_STATE.NORMAL) { } else if (state !== WND_STATE.NORMAL) {
wndState = WND_STATE.NORMAL; wndState = WND_STATE.NORMAL;
BrowserWindow.win.webContents.send('window-state-changed', 'normal')
BrowserWindow.win.webContents.executeJavaScript( BrowserWindow.win.webContents.executeJavaScript(
`app.chrome.maximized = false` `app.chrome.maximized = false`
); );
} }
}); });
}
let isQuiting = false let isQuiting = false
@ -1569,10 +1508,10 @@ export class BrowserWindow {
// Set window Handler // Set window Handler
BrowserWindow.win.webContents.setWindowOpenHandler((x: any) => { BrowserWindow.win.webContents.setWindowOpenHandler((x: any) => {
if (x.url.includes("apple") || x.url.includes("localhost")) { if (x.url.includes("apple") || x.url.includes("localhost")) {
return {action: "allow"}; return { action: "allow" };
} }
shell.openExternal(x.url).catch(console.error); shell.openExternal(x.url).catch(console.error);
return {action: "deny"}; return { action: "deny" };
}); });
} }
@ -1628,7 +1567,7 @@ export class BrowserWindow {
"CtlN": "Cider", "CtlN": "Cider",
"iV": "196623" "iV": "196623"
}; };
let server2 = mdns.createAdvertisement(x, `${await getPort({port: 3839})}`, { let server2 = mdns.createAdvertisement(x, `${await getPort({ port: 3839 })}`, {
name: encoded, name: encoded,
txt: txt_record txt: txt_record
}); });

View file

@ -16,10 +16,10 @@ 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 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 = {};
private static PluginMap: any = {};
constructor() { constructor() {
this.pluginsList = this.getPlugins(); this.pluginsList = this.getPlugins();
@ -104,10 +104,11 @@ export class Plugins {
public callPlugins(event: string, ...args: any[]) { public callPlugins(event: string, ...args: any[]) {
for (const plugin in this.pluginsList) { for (const plugin in this.pluginsList) {
if (this.pluginsList[plugin][event]) { if (this.pluginsList[plugin][event]) {
try{ try {
this.pluginsList[plugin][event](...args); this.pluginsList[plugin][event](...args);
}catch(e) { } catch (e) {
console.log(`[${plugin}] Plugin error: ${e}`); console.error(`[${plugin}] An error was encountered: ${e}`);
console.error(e)
} }
} }
} }

View file

@ -2,6 +2,7 @@ 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;
@ -12,15 +13,6 @@ export class Store {
}, },
"general": { "general": {
"close_button_hide": false, "close_button_hide": false,
"discordrpc": {
"enabled": true,
"client": "Cider",
"clear_on_pause": true,
"hide_buttons": false,
"hide_timestamp": false,
"state_format": "by {artist}",
"details_format": "{title}",
},
"language": "en_US", // electron.app.getLocale().replace('-', '_') this can be used in future "language": "en_US", // electron.app.getLocale().replace('-', '_') this can be used in future
"playbackNotifications": true, "playbackNotifications": true,
"resumeOnStartupBehavior": "local", "resumeOnStartupBehavior": "local",
@ -39,7 +31,8 @@ export class Store {
"applemusic": false, "applemusic": false,
"library": false, "library": false,
"amplaylists": false, "amplaylists": false,
"playlists": false "playlists": false,
"localLibrary": false
}, },
"onStartup": { "onStartup": {
"enabled": false, "enabled": false,
@ -66,7 +59,7 @@ export class Store {
"CommandOrControl", "CommandOrControl",
"G" "G"
], ],
"songs" : [ "songs": [
"CommandOrControl", "CommandOrControl",
"J" "J"
], ],
@ -89,17 +82,17 @@ export class Store {
], ],
"audioSettings": [ "audioSettings": [
"CommandOrControl", "CommandOrControl",
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift": "Alt"), process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift" : "Alt"),
"A" "A"
], ],
"pluginMenu": [ "pluginMenu": [
"CommandOrControl", "CommandOrControl",
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift": "Alt"), process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift" : "Alt"),
"P" "P"
], ],
"castToDevices": [ "castToDevices": [
"CommandOrControl", "CommandOrControl",
process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift": "Alt"), process.platform == "darwin" ? "Option" : (process.platform == "linux" ? "Shift" : "Alt"),
"C" "C"
], ],
"settings": [ "settings": [
@ -126,12 +119,35 @@ export class Store {
}, },
"showLovedTracksInline": true "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": {},
"secrets": {
"username": "",
"key": ""
}
},
},
"home": { "home": {
"followedArtists": [], "followedArtists": [],
"favoriteItems": [] "favoriteItems": []
}, },
"libraryPrefs": { "libraryPrefs": {
"songs": { "songs": {
"scroll": "infinite",
"sort": "name", "sort": "name",
"sortOrder": "asc", "sortOrder": "asc",
"size": "normal" "size": "normal"
@ -141,6 +157,7 @@ export class Store {
"sortOrder": "asc", "sortOrder": "asc",
"viewAs": "covers" "viewAs": "covers"
}, },
"localPaths": []
}, },
"audio": { "audio": {
"volume": 1, "volume": 1,
@ -151,18 +168,19 @@ export class Store {
"playbackRate": 1, "playbackRate": 1,
"quality": "HIGH", "quality": "HIGH",
"seamless_audio": true, "seamless_audio": true,
"normalization": false, "normalization": true,
"dBSPL": false, "dBSPL": false,
"dBSPLcalibration": 90, "dBSPLcalibration": 90,
"maikiwiAudio": { "maikiwiAudio": {
"ciderPPE": false, "ciderPPE": true,
"ciderPPE_value": "MAIKIWI", "ciderPPE_value": "MAIKIWI",
"opportunisticCorrection_state": "OFF",
"atmosphereRealizer1": false, "atmosphereRealizer1": false,
"atmosphereRealizer1_value": "NATURAL_STANDARD", "atmosphereRealizer1_value": "NATURAL_STANDARD",
"atmosphereRealizer2": false, "atmosphereRealizer2": false,
"atmosphereRealizer2_value": "NATURAL_STANDARD", "atmosphereRealizer2_value": "NATURAL_STANDARD",
"spatial": false, "spatial": false,
"spatialProfile": "71_420maikiwi", "spatialProfile": "BPLK",
"vibrantBass": { // Hard coded into the app. Don't include any of this config into exporting presets in store.ts "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], '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], '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],
@ -221,28 +239,22 @@ export class Store {
"windowColor": "#000000", "windowColor": "#000000",
"customAccentColor": false, "customAccentColor": false,
"accentColor": "#fc3c44", "accentColor": "#fc3c44",
"purplePodcastPlaybackBar": false "purplePodcastPlaybackBar": false,
"maxElementScale": -1 // -1 default, anything else is a custom scale
}, },
"lyrics": { "lyrics": {
"enable_mxm": false, "enable_mxm": true,
"mxm_karaoke": false, "mxm_karaoke": false,
"mxm_language": "en", "mxm_language": "disabled",
"enable_qq": false, "enable_qq": false,
"enable_yt": false, "enable_yt": false,
}, },
"lastfm": {
"enabled": false,
"scrobble_after": 30,
"auth_token": "",
"enabledRemoveFeaturingArtists": true,
"filterLoop": true,
"NowPlaying": "true"
},
"advanced": { "advanced": {
"AudioContext": false, "AudioContext": true,
"experiments": [], "experiments": [],
"playlistTrackMapping": true, "playlistTrackMapping": true,
"ffmpegLocation": "" "ffmpegLocation": "",
"disableLogging": true
}, },
"connectUser": { "connectUser": {
"auth": null, "auth": null,
@ -253,15 +265,9 @@ export class Store {
} }
}, },
} }
private migrations: any = { private migrations: any = {}
'>=1.4.3': (store: ElectronStore) => {
if (typeof store.get('general.discordrpc') == 'number' || typeof store.get('general.discordrpc') == 'string') {
store.delete('general.discordrpc');
}
},
}
private schema: ElectronStore.Schema<any> = { private schema: ElectronStore.Schema<any> = {
"general.discordrpc": { "connectivity.discord_rpc": {
type: 'object' type: 'object'
}, },
} }
@ -272,57 +278,13 @@ export class Store {
defaults: this.defaults, defaults: this.defaults,
schema: this.schema, schema: this.schema,
migrations: this.migrations, migrations: this.migrations,
clearInvalidConfig: true clearInvalidConfig: false //disabled for now
}); });
Store.cfg.set(this.mergeStore(this.defaults, Store.cfg.store)) Store.cfg.set(this.mergeStore(this.defaults, Store.cfg.store))
this.ipcHandler(); this.ipcHandler();
} }
/**
* Merge Configurations
* @param target The target configuration
* @param source The source configuration
*/
private mergeStore = (target: { [x: string]: any; }, source: { [x: string]: any; }) => {
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
for (const key of Object.keys(source)) {
if (key.includes('migrations')) {
continue;
}
if (source[key] instanceof Array) {
continue
}
if (source[key] instanceof Object) Object.assign(source[key], this.mergeStore(target[key], source[key]))
}
// Join `target` and modified `source`
Object.assign(target || {}, source)
return target
}
/**
* IPC Handler
*/
private ipcHandler(): void {
electron.ipcMain.handle('getStoreValue', (_event, key, defaultValue) => {
return (defaultValue ? Store.cfg.get(key, true) : Store.cfg.get(key));
});
electron.ipcMain.handle('setStoreValue', (_event, key, value) => {
Store.cfg.set(key, value);
});
electron.ipcMain.on('getStore', (event) => {
event.returnValue = Store.cfg.store
})
electron.ipcMain.on('setStore', (_event, store) => {
Store.cfg.store = store
})
}
static pushToCloud(): void { static pushToCloud(): void {
if (Store.cfg.get('connectUser.auth') === null) return; if (Store.cfg.get('connectUser.auth') === null) return;
var syncData = Object(); var syncData = Object();
@ -360,4 +322,46 @@ export class Store {
body: JSON.stringify(postBody) body: JSON.stringify(postBody)
}) })
} }
/**
* Merge Configurations
* @param target The target configuration
* @param source The source configuration
*/
private mergeStore = (target: { [x: string]: any; }, source: { [x: string]: any; }) => {
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
for (const key of Object.keys(source)) {
if (key.includes('migrations')) {
continue;
}
if (source[key] instanceof Array) {
continue
}
if (source[key] instanceof Object) Object.assign(source[key], this.mergeStore(target[key], source[key]))
}
// Join `target` and modified `source`
Object.assign(target || {}, source)
return target
}
/**
* IPC Handler
*/
private ipcHandler(): void {
electron.ipcMain.handle('getStoreValue', (_event, key, defaultValue) => {
return (defaultValue ? Store.cfg.get(key, true) : Store.cfg.get(key));
});
electron.ipcMain.handle('setStoreValue', (_event, key, value) => {
Store.cfg.set(key, value);
});
electron.ipcMain.on('getStore', (event) => {
event.returnValue = Store.cfg.store
})
electron.ipcMain.on('setStore', (_event, store) => {
Store.cfg.store = store
})
}
} }

View file

@ -2,18 +2,39 @@ 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, dialog, ipcMain, Notification, shell, BrowserWindow} from "electron"; import {app, BrowserWindow, ipcMain} from "electron";
import fetch from "electron-fetch"; import fetch from "electron-fetch";
import {AppImageUpdater, NsisUpdater} from "electron-updater";
import * as log from "electron-log";
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 * Paths for the application to use
*/ */
private static paths: any = { static paths: any = {
srcPath: path.join(__dirname, "../../src"), srcPath: path.join(__dirname, "../../src"),
rendererPath: path.join(__dirname, "../../src/renderer"), rendererPath: path.join(__dirname, "../../src/renderer"),
mainPath: path.join(__dirname, "../../src/main"), mainPath: path.join(__dirname, "../../src/main"),
@ -43,6 +64,21 @@ export class utils {
return 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. * Fetches the i18n locale for the given language.
* @param language {string} The language to fetch the locale for. * @param language {string} The language to fetch the locale for.
@ -90,7 +126,6 @@ export class utils {
return Store.cfg.store return Store.cfg.store
} }
/** /**
* Get the store instance * Get the store instance
* @returns {Store} * @returns {Store}
@ -116,10 +151,6 @@ export class utils {
return Store.pushToCloud return Store.pushToCloud
} }
/** /**
* Gets the browser window * Gets the browser window
*/ */
@ -138,25 +169,4 @@ export class utils {
static loadJSFrontend(path: string): void { static loadJSFrontend(path: string): void {
bw.win.webContents.executeJavaScript(fs.readFileSync(path, "utf8")); bw.win.webContents.executeJavaScript(fs.readFileSync(path, "utf8"));
} }
/**
* 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()")
}
}
} }

View file

@ -6,7 +6,6 @@
"pages/library-artists", "pages/library-artists",
"pages/browse", "pages/browse",
"pages/groupings", "pages/groupings",
"pages/settings",
"pages/installed-themes", "pages/installed-themes",
"pages/listen_now", "pages/listen_now",
"pages/radio", "pages/radio",
@ -60,6 +59,7 @@
"components/fullscreen", "components/fullscreen",
"components/miniplayer", "components/miniplayer",
"components/castmenu", "components/castmenu",
"components/pathmenu",
"components/airplay-modal", "components/airplay-modal",
"components/artist-chip", "components/artist-chip",
"components/hello-world", "components/hello-world",

View file

@ -69,6 +69,11 @@
"component": "<cider-playlist :data=\"showingPlaylist\"></cider-playlist>", "component": "<cider-playlist :data=\"showingPlaylist\"></cider-playlist>",
"condition": "page.includes('album_')" "condition": "page.includes('album_')"
}, },
{
"page": "social-profiles_",
"component": "<cider-socialprofile :data=\"showingPlaylist\"></cider-socialprofile>",
"condition": "$root.page.includes('social-profiles_')"
},
{ {
"page": "recordLabel_", "page": "recordLabel_",
"component": "<cider-recordlabel :data=\"showingPlaylist\"></cider-recordlabel>", "component": "<cider-recordlabel :data=\"showingPlaylist\"></cider-recordlabel>",

View file

@ -1,18 +1,18 @@
require('v8-compile-cache'); require("v8-compile-cache");
const {app, components, ipcMain} = require('electron');
import {join} from 'path';
import {join} from "path";
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"
// Analytics for debugging fun yeah. // Analytics for debugging fun yeah.
Sentry({ Sentry({
@ -32,13 +32,13 @@ const CiderPlug = new Plugins();
* App Event Handlers * App Event Handlers
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
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 () => {
@ -49,11 +49,11 @@ app.on('ready', () => {
console.log(gpuInfo) console.log(gpuInfo)
}) })
console.log('[Cider][Widevine] Status:', components.status()); console.log("[Cider][Widevine] Status:", components.status());
Cider.bwCreated(); Cider.bwCreated();
win.on("ready-to-show", () => { win.on("ready-to-show", () => {
console.debug('[Cider] Window is Ready.') console.debug("[Cider] Window is Ready.")
CiderPlug.callPlugins('onReady', win); CiderPlug.callPlugins("onReady", win);
win.show(); win.show();
}); });
}); });
@ -68,20 +68,16 @@ 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);
}); });
ipcMain.on('nowPlayingItemDidChangeLastFM', (_event, attributes) => { app.on("before-quit", () => {
CiderPlug.callPlugin('lastfm.js', 'nowPlayingItemDidChangeLastFM', attributes); CiderPlug.callPlugins("onBeforeQuit");
})
app.on('before-quit', () => {
CiderPlug.callPlugins('onBeforeQuit');
console.warn(`${app.getName()} exited.`); console.warn(`${app.getName()} exited.`);
}); });
@ -90,21 +86,21 @@ 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

@ -52,12 +52,38 @@ export default class DiscordRPC {
const self = this const self = this
this.connect(); this.connect();
console.debug(`[Plugin][${this.name}] Ready.`); console.debug(`[Plugin][${this.name}] Ready.`);
ipcMain.on('updateRPCImage', (_event, imageurl) => { ipcMain.on('updateRPCImage', async (_event, imageurl) => {
if (!this._utils.getStoreValue("general.privateEnabled")) { 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', { fetch('https://api.cider.sh/v1/images', {
method: 'POST', method: 'POST',
body: JSON.stringify({url: imageurl}), 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: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'User-Agent': _win.webContents.getUserAgent() 'User-Agent': _win.webContents.getUserAgent()
@ -69,12 +95,14 @@ export default class DiscordRPC {
self.setActivity(self._attributes) self.setActivity(self._attributes)
}) })
} }
}
}) })
ipcMain.on("reloadRPC", () => { ipcMain.on("reloadRPC", () => {
console.log(`[DiscordRPC][reload] Reloading DiscordRPC.`); console.log(`[DiscordRPC][reload] Reloading DiscordRPC.`);
this._client.destroy() this._client.destroy()
this._client.endlessLogin({clientId: this._utils.getStoreValue("general.discordrpc.client") === "Cider" ? '911790844204437504' : '886578863147192350'}) this._client.endlessLogin({clientId: this._utils.getStoreValue("connectivity.discord_rpc.client") === "Cider" ? '911790844204437504' : '886578863147192350'})
.then(() => { .then(() => {
this.ready = true this.ready = true
this._utils.getWindow().webContents.send("rpcReloaded", this._client.user) this._utils.getWindow().webContents.send("rpcReloaded", this._client.user)
@ -88,6 +116,7 @@ export default class DiscordRPC {
}) })
} }
/** /**
* Runs on app stop * Runs on app stop
*/ */
@ -125,7 +154,7 @@ export default class DiscordRPC {
* @private * @private
*/ */
private connect() { private connect() {
if (!this._utils.getStoreValue("general.discordrpc.enabled")) { if (!this._utils.getStoreValue("connectivity.discord_rpc.enabled")) {
return; return;
} }
@ -143,7 +172,7 @@ export default class DiscordRPC {
}) })
// Login to Discord // Login to Discord
this._client.endlessLogin({clientId: this._utils.getStoreValue("general.discordrpc.client") === "Cider" ? '911790844204437504' : '886578863147192350'}) this._client.endlessLogin({clientId: this._utils.getStoreValue("connectivity.discord_rpc.client") === "Cider" ? '911790844204437504' : '886578863147192350'})
.then(() => { .then(() => {
this.ready = true this.ready = true
}) })
@ -161,8 +190,8 @@ export default class DiscordRPC {
// Check if show buttons is (true) or (false) // Check if show buttons is (true) or (false)
let activity: Object = { let activity: Object = {
details: this._utils.getStoreValue("general.discordrpc.details_format"), details: this._utils.getStoreValue("connectivity.discord_rpc.details_format"),
state: this._utils.getStoreValue("general.discordrpc.state_format"), state: this._utils.getStoreValue("connectivity.discord_rpc.state_format"),
largeImageKey: attributes?.artwork?.url?.replace('{w}', '1024').replace('{h}', '1024'), largeImageKey: attributes?.artwork?.url?.replace('{w}', '1024').replace('{h}', '1024'),
largeImageText: attributes.albumName, largeImageText: attributes.albumName,
instance: false // Whether the activity is in a game session instance: false // Whether the activity is in a game session
@ -177,7 +206,7 @@ export default class DiscordRPC {
} }
// Set the activity // Set the activity
if (!attributes.status && this._utils.getStoreValue("general.discordrpc.clear_on_pause")) { if (!attributes.status && this._utils.getStoreValue("connectivity.discord_rpc.clear_on_pause")) {
this._client.clearActivity() this._client.clearActivity()
} else if (activity && this._activityCache !== activity) { } else if (activity && this._activityCache !== activity) {
this._client.setActivity(activity) this._client.setActivity(activity)
@ -191,7 +220,7 @@ export default class DiscordRPC {
private filterActivity(activity: any, attributes: any): Object { private filterActivity(activity: any, attributes: any): Object {
// Add the buttons if people want them // Add the buttons if people want them
if (!this._utils.getStoreValue("general.discordrpc.hide_buttons")) { if (!this._utils.getStoreValue("connectivity.discord_rpc.hide_buttons")) {
activity.buttons = [ activity.buttons = [
{label: 'Listen on Cider', url: attributes.url.cider}, {label: 'Listen on Cider', url: attributes.url.cider},
{label: 'View on Apple Music', url: attributes.url.appleMusic} {label: 'View on Apple Music', url: attributes.url.appleMusic}
@ -199,13 +228,13 @@ export default class DiscordRPC {
} }
// Add the timestamp if its playing and people want them // Add the timestamp if its playing and people want them
if (!this._utils.getStoreValue("general.discordrpc.hide_timestamp") && attributes.status) { if (!this._utils.getStoreValue("connectivity.discord_rpc.hide_timestamp") && attributes.status) {
activity.startTimestamp = Date.now() - (attributes?.durationInMillis - attributes?.remainingTime) activity.startTimestamp = Date.now() - (attributes?.durationInMillis - attributes?.remainingTime)
activity.endTimestamp = attributes.endTime activity.endTimestamp = attributes.endTime
} }
// If the user wants to keep the activity when paused // If the user wants to keep the activity when paused
if (!this._utils.getStoreValue("general.discordrpc.clear_on_pause")) { if (!this._utils.getStoreValue("connectivity.discord_rpc.clear_on_pause")) {
activity.smallImageKey = attributes.status ? 'play' : 'pause'; activity.smallImageKey = attributes.status ? 'play' : 'pause';
activity.smallImageText = attributes.status ? 'Playing' : 'Paused'; activity.smallImageText = attributes.status ? 'Playing' : 'Paused';
} }

View file

@ -1,278 +1,236 @@
import * as electron from 'electron'; export default class lastfm {
import * as fs from 'fs';
import {resolve} from 'path';
export default class LastFMPlugin { /**
private sessionPath = resolve(electron.app.getPath('userData'), 'session.json'); * Base Plugin Information
private apiCredentials = { */
public name: string = 'LastFM Plugin';
public version: string = '2.0.0';
public author: string = 'Core (Cider Collective)';
private _apiCredentials = {
key: "f9986d12aab5a0fe66193c559435ede3", key: "f9986d12aab5a0fe66193c559435ede3",
secret: "acba3c29bd5973efa38cc2f0b63cc625" secret: "acba3c29bd5973efa38cc2f0b63cc625"
} }
/** /**
* Private variables for interaction in plugins * Plugin Initialization
*/ */
private _win: any; private _lfm: any = null;
private _app: any; private _authenticated: boolean = false;
private _lastfm: any; private _scrobbleDelay: any = null;
private _store: any; private _utils: any = null;
private _timer: any; private _scrobbleCache: any = {};
private _nowPlayingCache: any = {};
private authenticateFromFile() {
let sessionData = require(this.sessionPath)
console.log("[LastFM][authenticateFromFile] Logging in with Session Info.")
this._lastfm.setSessionCredentials(sessionData.username, sessionData.key)
console.log("[LastFM][authenticateFromFile] Logged in.", sessionData.username, sessionData.key)
}
authenticate() {
try {
if (this._store.lastfm.auth_token) {
this._store.lastfm.enabled = true;
}
if (!this._store.lastfm.enabled || !this._store.lastfm.auth_token) {
this._store.lastfm.enabled = false;
return
}
/// dont move this require to top , app wont load
const LastfmAPI = require('lastfmapi');
const lfmAPI = new LastfmAPI({
'api_key': this.apiCredentials.key,
'secret': this.apiCredentials.secret
});
this._lastfm = Object.assign(lfmAPI, {cachedAttributes: false, cachedNowPlayingAttributes: false});
fs.stat(this.sessionPath, (err: any) => {
if (err) {
console.error("[LastFM][Session] Session file couldn't be opened or doesn't exist,", err)
console.log("[LastFM][Auth] Beginning authentication from configuration")
console.log("[LastFM][tk]", this._store.lastfm.auth_token)
this._lastfm.authenticate(this._store.lastfm.auth_token, (err: any, session: any) => {
if (err) {
throw err;
}
console.log("[LastFM] Successfully obtained LastFM session info,", session); // {"name": "LASTFM_USERNAME", "key": "THE_USER_SESSION_KEY"}
console.log("[LastFM] Saving session info to disk.")
let tempData = JSON.stringify(session)
fs.writeFile(this.sessionPath, tempData, (err: any) => {
if (err)
console.log("[LastFM][fs]", err)
else {
console.log("[LastFM][fs] File was written successfully.")
this.authenticateFromFile()
new electron.Notification({
title: electron.app.getName(),
body: "Successfully logged into LastFM using Authentication Key."
}).show()
}
})
});
} else {
this.authenticateFromFile()
}
})
} catch (err) {
console.log(err)
}
}
private scrobbleSong(attributes: any) {
if (this._timer) clearTimeout(this._timer);
var self = this;
this._timer = setTimeout(async () => {
const currentAttributes = attributes;
if (!self._lastfm || self._lastfm.cachedAttributes === attributes) {
return
}
if (self._lastfm.cachedAttributes) {
if (self._lastfm.cachedAttributes.playParams.id === attributes.playParams.id) return;
}
const artist = await this.getPrimaryArtist(attributes)
const album = this.getAlbumName(attributes)
if (currentAttributes.status && currentAttributes === attributes) {
if (fs.existsSync(this.sessionPath)) {
// Scrobble playing song.
if (attributes.status === true) {
self._lastfm.track.scrobble({
'artist': artist,
'track': attributes.name,
'album': album,
'albumArtist': artist,
'timestamp': new Date().getTime() / 1000
}, function (err: any, scrobbled: any) {
if (err) {
return console.error('[LastFM] An error occurred while scrobbling', err);
}
console.log('[LastFM] Successfully scrobbled: ', scrobbled);
});
self._lastfm.cachedAttributes = attributes
}
} else {
self.authenticate();
}
} else {
return console.log('[LastFM] Did not add ', attributes.name, '—', artist, 'because now playing a other song.');
}
}, Math.round(attributes.durationInMillis * Math.min((self._store.lastfm.scrobble_after / 100), 0.8)));
}
private async updateNowPlayingSong(attributes: any) {
if (!this._lastfm || this._lastfm.cachedNowPlayingAttributes === attributes || !this._store.lastfm.NowPlaying) {
return
}
if (this._lastfm.cachedNowPlayingAttributes) {
if (this._lastfm.cachedNowPlayingAttributes.playParams.id === attributes.playParams.id) return;
}
if (fs.existsSync(this.sessionPath)) {
const artist = await this.getPrimaryArtist(attributes)
const album = this.getAlbumName(attributes)
// update Now Playing
if (attributes.status === true) {
this._lastfm.track.updateNowPlaying({
'artist': artist,
'track': attributes.name,
'album': album,
'albumArtist': artist
}, function (err: any, nowPlaying: any) {
if (err) {
return console.error('[LastFM] An error occurred while updating nowPlayingSong', err);
}
console.log('[LastFM] Successfully updated nowPlayingSong', nowPlaying);
});
this._lastfm.cachedNowPlayingAttributes = attributes
}
} else {
this.authenticate()
}
}
private getAlbumName(attributes: any): string {
return attributes.albumName.replace(/ - Single| - EP/g, '');
}
private async getPrimaryArtist(attributes: any) {
const songId = attributes.playParams.catalogId || attributes.playParams.id
if (!this._store.lastfm.enabledRemoveFeaturingArtists || !songId) return attributes.artistName;
const res = await this._win.webContents.executeJavaScript(`
(async () => {
const subMk = await MusicKit.getInstance().api.v3.music("/v1/catalog/" + MusicKit.getInstance().storefrontId + "/songs/${songId}", {
include: {
songs: ["artists"]
}
})
if (!subMk) console.error('[LastFM] Request failed: /v1/catalog/us/songs/${songId}')
return subMk.data
})()
`).catch(console.error)
if (!res) return attributes.artistName
const data = res.data
if (!data.length) {
console.error(`[LastFM] Unable to locate song with id of ${songId}`)
return attributes.artistName;
}
const artists = res.data[0].relationships.artists.data
if (!artists.length) {
console.error(`[LastFM] Unable to find artists related to the song with id of ${songId}`)
return attributes.artistName;
}
const primaryArtist = artists[0]
return primaryArtist.attributes.name
}
/** /**
* Base Plugin Details (Eventually implemented into a GUI in settings) * Public Methods
*/ */
public name: string = 'LastFMPlugin';
public description: string = 'LastFM plugin for Cider';
public version: string = '0.0.1';
public author: string = 'vapormusic / Cider Collective';
/** constructor(utils: any) {
* Runs on plugin load (Currently run on application start) this._utils = utils;
*/
constructor(utils: { getApp: () => any; getStore: () => any; }) {
this._app = utils.getApp();
this._store = utils.getStore()
utils.getApp().on('second-instance', (_e: any, argv: any) => {
// Checks if first instance is authorized and if second instance has protocol args
argv.forEach((value: any) => {
if (value.includes('auth')) {
console.log('[LastFMPlugin ok]')
let authURI = String(argv).split('/auth/')[1];
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
const authKey = authURI.split('lastfm?token=')[1];
this._store.lastfm.enabled = true;
this._store.lastfm.auth_token = authKey;
console.log(authKey);
this._win.webContents.send('LastfmAuthenticated', authKey);
this.authenticate();
}
} }
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)
}) })
electron.app.on('open-url', (event: any, arg: any) => {
console.log('[LastFMPlugin] yes') this._utils.getIPCMain().on('lastfm:disconnect', (_event: any) => {
event.preventDefault(); this._lfm.setSessionCredentials(null, null);
if (arg.includes('auth')) { this._authenticated = false;
let authURI = String(arg).split('/auth/')[1]; console.debug(`[${lastfm.name}:disconnect] Disconnected`)
if (authURI.startsWith('lastfm')) { // If we wanted more auth options })
const authKey = authURI.split('lastfm?token=')[1];
this._store.lastfm.enabled = true; this._utils.getIPCMain().on('lastfm:nowPlayingChange', (event: any, attributes: any) => {
this._store.lastfm.auth_token = authKey; if (this._utils.getStoreValue("connectivity.lastfm.filter_loop") || this._utils.getStoreValue("general.privateEnabled")) return;
this._win.webContents.send('LastfmAuthenticated', authKey); this.updateNowPlayingTrack(attributes)
console.log(authKey); })
this.authenticate();
} this._utils.getIPCMain().on('lastfm:scrobbleTrack', (event: any, attributes: any) => {
} if (this._utils.getStoreValue("general.privateEnabled")) return;
this.scrobbleTrack(attributes)
}) })
} }
/** /**
* Runs on app ready * Runs on playback State Change
* @param attributes Music Attributes (attributes.status = current state)
*/ */
onReady(win: any): void { onPlaybackStateDidChange(attributes: object): void {
this._win = win;
this.authenticate();
}
/**
* Runs on app stop
*/
onBeforeQuit(): void {
console.log('Example plugin stopped');
} }
/** /**
* Runs on song change * Runs on song change
* @param attributes Music Attributes * @param attributes Music Attributes
* @param scrobble
*/ */
nowPlayingItemDidChangeLastFM(attributes: any): void { onNowPlayingItemDidChange(attributes: any, scrobble = false): void {
if (!this._store.general.privateEnabled) { if (this._utils.getStoreValue("general.privateEnabled")) return;
attributes.status = true this.updateNowPlayingTrack(attributes)
if (!this._store.lastfm.filterLoop) {
this._lastfm.cachedNowPlayingAttributes = false;
this._lastfm.cachedAttributes = false
} }
this.updateNowPlayingSong(attributes)
this.scrobbleSong(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)
} }
} }
/**
* 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}`);
this._utils.getWindow().webContents.executeJavaScript(`app.notyf.error("${err.message}");`)
return;
}
this._utils.getWindow().webContents.send('lastfm:authenticated', session)
this._authenticated = true;
console.debug(`[${lastfm.name}:authenticate] Authenticated as ${session.username}`)
});
}
/**
* Verifies the track information with lastfm
* @param attributes
* @param callback
* @private
*/
private verifyTrack(attributes: any, callback: Function): void {
if (!attributes) return attributes;
if (!attributes.lfmAlbum) {
this._lfm.album.getInfo({
"artist": attributes.artistName,
"album": attributes.albumName
}, (err: any, data: any) => {
if (err) {
console.error(`[${lastfm.name}] [album.getInfo] Error: ${typeof err === "string" ? err : err.message}`)
return {};
}
if (data) {
attributes.lfmAlbum = data
callback(attributes)
}
})
} else {
this._lfm.track.getCorrection(attributes.artistName, 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
}
});
}
} }

View file

@ -37,7 +37,7 @@ export default class Thumbar {
{ {
label: utils.getLocale(utils.getStoreValue('general.language'), 'term.settings'), label: utils.getLocale(utils.getStoreValue('general.language'), 'term.settings'),
accelerator: utils.getStoreValue("general.keybindings.settings").join('+'), accelerator: utils.getStoreValue("general.keybindings.settings").join('+'),
click: () => utils.getWindow().webContents.executeJavaScript(`app.appRoute('settings')`) click: () => utils.getWindow().webContents.executeJavaScript(`app.openSettingsPage()`)
}, },
...(this.isMac ? [ ...(this.isMac ? [
{type: 'separator'}, {type: 'separator'},

View file

@ -6,7 +6,10 @@ 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
*/
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)
*/ */
@ -15,30 +18,17 @@ export default class mpris {
public version: string = '1.0.0'; public version: string = '1.0.0';
public author: string = 'Core'; public author: string = 'Core';
/**
* MPRIS Service
*/
private static player: Player.Player;
private static mprisEvents: Object = {
"playpause": "playPause",
"play": "play",
"pause": "pause",
"next": "next",
"previous": "previous",
}
/******************************************************************************************* /*******************************************************************************************
* Private Methods * Private Methods
* ****************************************************************************************/ * ****************************************************************************************/
/** /**
* Runs a media event * Runs on plugin load (Currently run on application start)
* @param type - pausePlay, next, previous
* @private
*/ */
private static runMediaEvent(type: string) { constructor(utils: any) {
console.debug(`[Plugin][${this.name}] ${type}.`); mpris.utils = utils
mpris.utils.getWindow().webContents.executeJavaScript(`MusicKitInterop.${type}()`).catch(console.error)
console.debug(`[Plugin][${mpris.name}] Loading Complete.`);
} }
/** /**
@ -54,7 +44,6 @@ export default class mpris {
} }
} }
/** /**
* Connects to MPRIS Service * Connects to MPRIS Service
*/ */
@ -63,29 +52,49 @@ export default class mpris {
const player = Player({ const player = Player({
name: 'cider', name: 'cider',
identity: 'Cider', identity: 'Cider',
supportedUriSchemes: [],
supportedMimeTypes: [],
supportedInterfaces: ['player'] supportedInterfaces: ['player']
}); });
console.debug(`[Plugin][${mpris.name}] Successfully connected.`); console.debug(`[${mpris.name}:connect] Successfully connected.`);
const pos_atr = {durationInMillis: 0}; const renderer = mpris.utils.getWindow().webContents
player.getPosition = function () { const loopType: { [key: string]: number; } = {
const durationInMicro = pos_atr.durationInMillis * 1000; 'none': 0,
const percentage = parseFloat("0") || 0; 'track': 1,
return durationInMicro * percentage; 'playlist': 2,
} }
for (const [key, value] of Object.entries(mpris.mprisEvents)) { player.on('next', () => mpris.utils.playback.next())
player.on(key, function () { player.on('previous', () => mpris.utils.playback.previous())
mpris.runMediaEvent(value) 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'))
player.on('quit', function () { mpris.utils.getIPCMain().on('mpris:playbackTimeDidChange', (event: any, time: number) => {
process.exit(); 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; mpris.player = player;
} }
@ -93,9 +102,9 @@ export default class mpris {
/** /**
* Update M.P.R.I.S Player Attributes * Update M.P.R.I.S Player Attributes
*/ */
private static updatePlayer(attributes: any) { private static updateMetaData(attributes: any) {
const MetaData = { mpris.player.metadata = {
'mpris:trackid': mpris.player.objectPath(`track/${attributes.playParams.id.replace(/[.]+/g, "")}`), 'mpris:trackid': mpris.player.objectPath(`track/${attributes.playParams.id.replace(/[.]+/g, "")}`),
'mpris:length': attributes.durationInMillis * 1000, // In microseconds 'mpris:length': attributes.durationInMillis * 1000, // In microseconds
'mpris:artUrl': (attributes.artwork.url.replace('/{w}x{h}bb', '/512x512bb')).replace('/2000x2000bb', '/35x35bb'), 'mpris:artUrl': (attributes.artwork.url.replace('/{w}x{h}bb', '/512x512bb')).replace('/2000x2000bb', '/35x35bb'),
@ -103,33 +112,12 @@ export default class mpris {
'xesam:album': `${attributes.albumName}`, 'xesam:album': `${attributes.albumName}`,
'xesam:artist': [`${attributes.artistName}`], 'xesam:artist': [`${attributes.artistName}`],
'xesam:genre': attributes.genreNames 'xesam:genre': attributes.genreNames
};
} }
if (mpris.player.metadata["mpris:trackid"] === MetaData["mpris:trackid"]) { /*******************************************************************************************
return * Public Methods
} * ****************************************************************************************/
mpris.player.metadata = MetaData;
}
/**
* Update M.P.R.I.S Player State
* @private
* @param attributes
*/
private static updatePlayerState(attributes: any) {
switch (attributes.status) {
case true: // Playing
mpris.player.playbackStatus = Player.PLAYBACK_STATUS_PLAYING;
break;
case false: // Paused
mpris.player.playbackStatus = Player.PLAYBACK_STATUS_PAUSED;
break;
default:
mpris.player.playbackStatus = Player.PLAYBACK_STATUS_STOPPED;
break
}
}
/** /**
* Clear state * Clear state
@ -143,26 +131,12 @@ export default class mpris {
mpris.player.playbackStatus = Player.PLAYBACK_STATUS_STOPPED; mpris.player.playbackStatus = Player.PLAYBACK_STATUS_STOPPED;
} }
/*******************************************************************************************
* Public Methods
* ****************************************************************************************/
/**
* Runs on plugin load (Currently run on application start)
*/
constructor(utils: any) {
mpris.utils = utils
console.debug(`[Plugin][${mpris.name}] Loading Complete.`);
}
/** /**
* Runs on app ready * Runs on app ready
*/ */
@mpris.linuxOnly @mpris.linuxOnly
onReady(_: any): void { onReady(_: any): void {
console.debug(`[Plugin][${mpris.name}] Ready.`); console.debug(`[${mpris.name}:onReady] Ready.`);
} }
/** /**
@ -187,9 +161,8 @@ export default class mpris {
* @param attributes Music Attributes (attributes.status = current state) * @param attributes Music Attributes (attributes.status = current state)
*/ */
@mpris.linuxOnly @mpris.linuxOnly
onPlaybackStateDidChange(attributes: object): void { onPlaybackStateDidChange(attributes: any): void {
console.debug(`[Plugin][${mpris.name}] onPlaybackStateDidChange.`); mpris.player.playbackStatus = attributes?.status ? Player.PLAYBACK_STATUS_PLAYING : Player.PLAYBACK_STATUS_PAUSED
mpris.updatePlayerState(attributes)
} }
/** /**
@ -198,8 +171,7 @@ export default class mpris {
*/ */
@mpris.linuxOnly @mpris.linuxOnly
onNowPlayingItemDidChange(attributes: object): void { onNowPlayingItemDidChange(attributes: object): void {
console.debug(`[Plugin][${mpris.name}] onMetadataDidChange.`); mpris.updateMetaData(attributes);
mpris.updatePlayer(attributes);
} }
} }

View file

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

View file

@ -0,0 +1,257 @@
import { ProviderDB } from "./db";
import * as path from 'path';
const { readdir } = require('fs').promises;
import { utils } from '../../base/utils';
import * as mm from 'music-metadata';
import {Md5} from 'ts-md5/dist/md5';
import e from "express";
import { EventEmitter } from 'events';
import { parseFile, recursiveFolderSearch } from 'cider_utils';
export class LocalFiles {
static localSongs: any = [];
static localSongsArts: any = [];
public static DB = ProviderDB.db;
static eventEmitter = new EventEmitter();
static getDataType(item_id : String | any){
if ((item_id ?? ('')).startsWith('ciderlocalart'))
return 'artwork'
else if ((item_id ?? ('')).startsWith('ciderlocal'))
return 'track'
}
static 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 = []
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')}
let metadatalist = []
let metadatalistart = []
let numid = 0;
// 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 ?? audio.substring(audio.lastIndexOf('\\') + 1),
"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;
}
static async cleanUpDB(){
let folders = utils.getStoreValue("libraryPrefs.localPaths")
let rows = (await ProviderDB.db.allDocs({include_docs: true,
attachments: true})).rows.map((item: any)=>{return item.doc})
let tracks = rows.filter((item: any) => {return this.getDataType(item._id) == "track" && !folders.some((i: String) => {return item["attributes"]["assetUrl"].startsWith("file:///" + i)})})
let hashs = tracks.map((i: any) => {return i._id})
for (let hash of hashs){
try{
ProviderDB.db.get(hash).then(function (doc: any) {
return ProviderDB.db.remove(doc);
});} catch(e){}
try{
ProviderDB.db.get(hash.replace('ciderlocal','ciderlocalart')).then(function (doc: any) {
return ProviderDB.db.remove(doc);
});} catch(e){}
}
}
static 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 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

@ -11,9 +11,6 @@ const MusicKitInterop = {
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);
// if (typeof _plugins != "undefined") {
// _plugins.execute("OnPlaybackStateChanged", {Attributes: MusicKitInterop.getAttributes()})
// }
} }
}); });
@ -23,19 +20,18 @@ const MusicKitInterop = {
}); });
/** wsapi */ /** wsapi */
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackTimeDidChange, () => {
ipcRenderer.send('mpris:playbackTimeDidChange', (MusicKit.getInstance()?.currentPlaybackTime * 1000 * 1000 ) ?? 0);
})
MusicKit.getInstance().addEventListener(MusicKit.Events.nowPlayingItemDidChange, async () => { MusicKit.getInstance().addEventListener(MusicKit.Events.nowPlayingItemDidChange, async () => {
console.debug('nowPlayingItemDidChange') console.debug('[cider:preload] nowPlayingItemDidChange')
const attributes = MusicKitInterop.getAttributes() const attributes = MusicKitInterop.getAttributes()
const trackFilter = MusicKitInterop.filterTrack(attributes, false, true)
if (trackFilter) { 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") {
global.ipcRenderer.send('lastfm:nowPlayingChange', attributes);
// LastFM's Custom Call
await MusicKitInterop.modifyNamesOnLocale();
if (trackFilter || !app.cfg.lastfm.filterLoop) {
global.ipcRenderer.send('nowPlayingItemDidChangeLastFM', attributes);
} }
if (MusicKit.getInstance().nowPlayingItem) { if (MusicKit.getInstance().nowPlayingItem) {
@ -46,11 +42,19 @@ const MusicKitInterop = {
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.getInstance().addEventListener(MusicKit.Events.mediaPlaybackError, (e) => { MusicKit.getInstance().addEventListener(MusicKit.Events.mediaPlaybackError, (e) => {
console.warn(`[mediaPlaybackError] ${e}`); console.warn(`[cider:preload] mediaPlaybackError] ${e}`);
}) });
MusicKit.getInstance().addEventListener(MusicKit.Events.shuffleModeDidChange, () => {
global.ipcRenderer.send('shuffleModeDidChange', MusicKit.getInstance().shuffleMode)
});
MusicKit.getInstance().addEventListener(MusicKit.Events.repeatModeDidChange, () => {
global.ipcRenderer.send('repeatModeDidChange', MusicKit.getInstance().repeatMode)
});
}, },
sleep(ms) { sleep(ms) {
@ -59,28 +63,6 @@ const MusicKitInterop = {
}); });
}, },
async modifyNamesOnLocale() {
if (app.mklang === '' || app.mklang == null) {
return;
}
const mk = MusicKit.getInstance()
const nowPlayingItem = mk.nowPlayingItem;
if ((nowPlayingItem?._songId ?? nowPlayingItem?.songId) == null){
return;
}
const id = nowPlayingItem?._songId ?? (nowPlayingItem?.songId ?? nowPlayingItem?.id)
if (id != null && id !== -1) {
try{
const query = await mk.api.v3.music(`/v1${(((nowPlayingItem?._songId ?? nowPlayingItem?.songId) != null) && ((nowPlayingItem?._songId ?? nowPlayingItem?.songId) !== -1)) ? `/catalog/${mk.storefrontId}/` : `/me/library/`}songs/${id}?l=${app.mklang}`);
if (query?.data?.data[0]){
let attrs = query?.data?.data[0]?.attributes;
if (attrs?.name) { nowPlayingItem.attributes.name = attrs?.name ?? ''}
if (attrs?.albumName) { nowPlayingItem.attributes.albumName = attrs?.albumName ?? ''}
if (attrs?.artistName) { nowPlayingItem.attributes.artistName = attrs?.artistName ?? ''}
}} catch (e) { }
} else {}
},
getAttributes: function () { getAttributes: function () {
const mk = MusicKit.getInstance() const mk = MusicKit.getInstance()
const nowPlayingItem = mk.nowPlayingItem; const nowPlayingItem = mk.nowPlayingItem;
@ -96,8 +78,8 @@ const MusicKitInterop = {
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';
@ -109,6 +91,7 @@ const MusicKitInterop = {
? remainingTimeExport * 1000 ? remainingTimeExport * 1000
: 0; : 0;
attributes.durationInMillis = attributes?.durationInMillis ?? 0; attributes.durationInMillis = attributes?.durationInMillis ?? 0;
attributes.currentPlaybackTime = mk?.currentPlaybackTime ?? 0;
attributes.currentPlaybackProgress = currentPlaybackProgress ?? 0; attributes.currentPlaybackProgress = currentPlaybackProgress ?? 0;
attributes.startTime = Date.now(); attributes.startTime = Date.now();
attributes.endTime = Math.round( attributes.endTime = Math.round(
@ -156,19 +139,19 @@ const MusicKitInterop = {
// } 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(`[MusicKitInterop.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(`[MusicKitInterop.previous] Skipping to Previous ${r}`)); MusicKit.getInstance().skipToPreviousItem().then(r => console.debug(`[cider:preload] [previous] Skipping to Previous ${r}`));
} }
} }
process.once('loaded', () => { process.once('loaded', () => {
console.debug("Setting ipcRenderer") console.debug("[cider:preload] IPC Listeners Created!")
global.MusicKitInterop = MusicKitInterop; global.MusicKitInterop = MusicKitInterop;
}); });

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="white" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M77.25 256l137.4-137.4c12.5-12.5 12.5-32.75 0-45.25s-32.75-12.5-45.25 0l-160 160c-12.5 12.5-12.5 32.75 0 45.25l160 160C175.6 444.9 183.8 448 192 448s16.38-3.125 22.62-9.375c12.5-12.5 12.5-32.75 0-45.25L77.25 256zM269.3 256l137.4-137.4c12.5-12.5 12.5-32.75 0-45.25s-32.75-12.5-45.25 0l-160 160c-12.5 12.5-12.5 32.75 0 45.25l160 160C367.6 444.9 375.8 448 384 448s16.38-3.125 22.62-9.375c12.5-12.5 12.5-32.75 0-45.25L269.3 256z"/></svg>

After

Width:  |  Height:  |  Size: 685 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="white" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M246.6 233.4l-160-160c-12.5-12.5-32.75-12.5-45.25 0s-12.5 32.75 0 45.25L178.8 256l-137.4 137.4c-12.5 12.5-12.5 32.75 0 45.25C47.63 444.9 55.81 448 64 448s16.38-3.125 22.62-9.375l160-160C259.1 266.1 259.1 245.9 246.6 233.4zM438.6 233.4l-160-160c-12.5-12.5-32.75-12.5-45.25 0s-12.5 32.75 0 45.25L370.8 256l-137.4 137.4c-12.5 12.5-12.5 32.75 0 45.25C239.6 444.9 247.8 448 256 448s16.38-3.125 22.62-9.375l160-160C451.1 266.1 451.1 245.9 438.6 233.4z"/></svg>

After

Width:  |  Height:  |  Size: 706 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="white" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M96 480c-8.188 0-16.38-3.125-22.62-9.375c-12.5-12.5-12.5-32.75 0-45.25L242.8 256L73.38 86.63c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0l192 192c12.5 12.5 12.5 32.75 0 45.25l-192 192C112.4 476.9 104.2 480 96 480z"/></svg>

After

Width:  |  Height:  |  Size: 478 B

View file

@ -1,10 +1,41 @@
<svg width="71" height="55" viewBox="0 0 71 55" fill="none" xmlns="http://www.w3.org/2000/svg"> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<g clip-path="url(#clip0)"> <svg
<path d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z" fill="#ffffff"/> width="16"
</g> height="16"
<defs> fill="currentColor"
<clipPath id="clip0"> class="bi bi-discord"
<rect width="71" height="55" fill="white"/> viewBox="0 0 16 16"
</clipPath> version="1.1"
</defs> id="svg4"
sodipodi:docname="discord.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="51.8125"
inkscape:cx="7.5946924"
inkscape:cy="8.0096502"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612Zm5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612Z"
id="path2"
style="fill:#ffffff;fill-opacity:1" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-hard-drive">
<path d="M22 12H2m3.45-6.89L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11zM6 16h.01M10 16h.01"/>
</svg>

After

Width:  |  Height:  |  Size: 372 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-headphones">
<path d="M3 18v-6a9 9 0 0 1 18 0v6"/>
<path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"/>
</svg>

After

Width:  |  Height:  |  Size: 390 B

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-pen-tool" width="20" height="20">
<path d="m12 19 7-7 3 3-7 7-3-3z"/>
<path d="m18 13-1.5-7.5L2 2l3.5 14.5L13 18l5-5zM2 2l7.586 7.586"/>
<circle cx="11" cy="11" r="2"/>
</svg>

After

Width:  |  Height:  |  Size: 363 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-package"><line x1="16.5" y1="9.4" x2="7.5" y2="4.21"></line><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>

After

Width:  |  Height:  |  Size: 517 B

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
version="1.1"
id="svg4"
sodipodi:docname="style-svgrepo-com.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="36.541667"
inkscape:cx="8.031927"
inkscape:cy="12.054732"
inkscape:window-width="1920"
inkscape:window-height="1044"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M23.14.93l-.07-.07A2.926 2.926 0 0 0 20.98 0a2.886 2.886 0 0 0-2.08.86L8.858 10.9a3.04 3.04 0 0 0-.53.72 7.793 7.793 0 0 0-4.1 1.621c-.191.144-.36.316-.5.51a6.08 6.08 0 0 0-.98 1.961c-.25.69-.59 1.631-1.22 3-.42.91-.75 1.541-.98 1.981a3.092 3.092 0 0 0-.54 1.631c.014.206.08.406.19.58a2.64 2.64 0 0 0 2.23 1.07 10.462 10.462 0 0 0 8.161-3.371c.378-.44.692-.932.93-1.461a7.882 7.882 0 0 0 .69-3.361.142.142 0 0 1 .02-.04c.325-.144.62-.347.87-.6L23.14 5.1A2.888 2.888 0 0 0 24 3.021 2.927 2.927 0 0 0 23.14.93zM9.7 18.317c-.17.368-.388.711-.65 1.02a8.393 8.393 0 0 1-6.891 2.6c.05-.1.11-.21.17-.32.24-.46.58-1.11 1.02-2.061a39.058 39.058 0 0 0 1.28-3.151c.14-.491.355-.957.64-1.381.062-.08.133-.154.21-.22a5.221 5.221 0 0 1 2.59-1.14c.121.537.396 1.027.79 1.411l.07.07c.35.357.788.616 1.27.75a5.614 5.614 0 0 1-.499 2.422zM21.73 3.691L11.678 13.735a.947.947 0 0 1-.67.28.983.983 0 0 1-.67-.28l-.07-.07a.948.948 0 0 1 0-1.34L20.309 2.271c.18-.173.42-.27.671-.271a.937.937 0 0 1 .67.27l.08.08c.36.374.36.967 0 1.341z"
fill="#494c4e"
fill-rule="evenodd"
id="path2"
style="fill:#000000;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-zap" width="20" height="20">
<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/>
</svg>

After

Width:  |  Height:  |  Size: 263 B

View file

@ -1,3 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/> <svg
width="16"
height="16"
fill="currentColor"
class="bi bi-github"
viewBox="0 0 16 16"
version="1.1"
id="svg4"
sodipodi:docname="github.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="51.8125"
inkscape:cx="7.5946924"
inkscape:cy="8.0096502"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
id="path2"
style="fill:#ffffff;fill-opacity:1" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 818 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Before After
Before After

View file

@ -1 +1,4 @@
<svg fill="none" height="391" viewBox="0 0 391 391" width="391" xmlns="http://www.w3.org/2000/svg"><path d="m52 102h296v199h-296z" fill="#fff"/><path d="m174 1.09995c-79.2999 8.9-145.5999 65.29995-166.59995 141.70005-15.2 55.6-5.8 114 26.10005 161.7 61.7 92.3 189.4999 114.2 278.7999 47.8 74.8-55.8 99.8-156.6 59.7-241-9.7-20.3001-20.4-35.6001-36.4-52.2001-27.2-27.9-61.3-46.7-99.6-54.99995-18.3-3.900005-43.3-5.1-62-3zm117.8 102.90005c25.2 7 43.6 24.6 50.2 48 7.8 28.1-.1 55.6-21.5 73.9-11.5 9.8-32.5 17.1-49.6 17.1h-7.6l-.8 7.2c-1.9 19.4-11.4 32.5-27 37-2.9.9-25.2 1.2-80 1.2l-75.9999.1-6-2.8c-9.6-4.5-16.9-12.7-19.7-22.2-.4-1.6-.8-36.6-.8-77.8 0-80.7 0-80.6 5.2-82.7 1.4-.5 48.5999-.9 114.2999-.9 109.5-.1 112.2-.1 119.3 1.9z" fill="#579fbf"/><path d="m264 173v34h8.8c15.5 0 25-5.3 31.2-17.4 3.3-6.6 3.5-7.4 3.5-17.5 0-9.6-.3-11.1-2.8-16.3-3-6.2-8.6-11.6-15.2-14.8-3.3-1.6-6.2-2-14.8-2h-10.7z" fill="#579fbf"/><path d="m177.885 147.41c-7.3 2.4-9.6 3.7-15.3 8.4l-4.7 3.9-4.3-3.5c-14.2-11.6-35-12.9-46.7-2.9-5.8 5-8.1996 10.3-8.7996 19.4-.5 8.1 1.2 15.8 4.9996 23.2 1.1 2.2 5.9 8 10.6 13 14.7 15.3 42.4 40.7 44.4 40.7 1.2 0 12.1-10.1 27.3-25.3 22.9-22.8 25.7-25.9 28.6-32.1 4.1-8.7 5-18.8 2.5-26.4-4.9-14.6-23.4-23.4-38.6-18.4z" fill="#ff5f5f"/></svg> <svg width="38" height="38" viewBox="0 0 600 400" xmlns="http://www.w3.org/2000/svg">
<path style="fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:#000;stroke-width:13;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" d="M464.7 222.884c-21.905 2.737-39.702.683-39.702.683V89.44h41.754s46.548 13 46.548 62.273c0 45.165-23.272 62.959-48.6 71.171m115.382-94.16C561.821 32.277 465.385 20.292 465.385 20.292H33.42c-14.266 0-16.021 18.833-16.021 18.833s-1.924 172.892-.529 279.07c3.87 57.21 61.058 63.076 61.058 63.076s195.155-.57 282.462-1.14c57.558-10.083 63.339-60.573 62.769-88.188 102.713 5.707 175.182-66.771 156.923-163.219"/>
<path style="fill:#ff5f5f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00346" d="M216.99 306.032c4.46 2.246 7.31-.544 7.31-.544s65.27-59.573 94.674-93.882c26.154-30.691 27.858-82.413-17.056-101.739-44.913-19.324-81.866 22.736-81.866 22.736-32.046-35.245-80.546-33.46-102.979-9.608-22.431 23.852-14.598 64.791 2.137 87.576 15.709 21.388 84.757 82.93 95.222 93.338 0 0 .763.798 2.557 2.123"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

@ -1 +1,50 @@
<svg height="38" viewBox="0 0 38 38" width="38" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="m34.6194245 8.17880011c2.1314244 3.07072649 3.3805755 6.80008579 3.3805755 10.82119989s-1.2491511 7.7504734-3.3805755 10.8211999l-4.9217466-4.9217466c.9664963-1.748848 1.5166078-3.7599079 1.5166078-5.8994533s-.5501115-4.1506053-1.5166078-5.8994533zm-4.7982246-4.79822459-4.9217466 4.92174663c-1.748848-.96649637-3.7599079-1.51660786-5.8994533-1.51660786-6.7457637 0-12.21428571 5.46852201-12.21428571 12.21428571s5.46852201 12.2142857 12.21428571 12.2142857c2.1395454 0 4.1506053-.5501115 5.8994533-1.5166078l4.9217466 4.9217466c-3.0707265 2.1314244-6.8000858 3.3805755-10.8211999 3.3805755-10.49341025 0-19-8.5065898-19-19 0-10.49341025 8.50658975-19 19-19 4.0211141 0 7.7504734 1.24915112 10.8211999 3.38057552z" fill="#7fadf2"/><path d="m34.6194245 8.17880011c2.1314244 3.07072649 3.3805755 6.80008579 3.3805755 10.82119989s-1.2491511 7.7504734-3.3805755 10.8211999l-4.9217466-4.9217466c.9664963-1.748848 1.5166078-3.7599079 1.5166078-5.8994533s-.5501115-4.1506053-1.5166078-5.8994533z" fill="#b8d3f4"/></g></svg> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="38"
viewBox="0 0 38 38"
width="38"
version="1.1"
id="svg8"
sodipodi:docname="open_collective.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs12" />
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="21.815789"
inkscape:cx="11.665862"
inkscape:cy="18.977081"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g6" />
<g
fill="none"
fill-rule="evenodd"
id="g6">
<path
d="m34.6194245 8.17880011c2.1314244 3.07072649 3.3805755 6.80008579 3.3805755 10.82119989s-1.2491511 7.7504734-3.3805755 10.8211999l-4.9217466-4.9217466c.9664963-1.748848 1.5166078-3.7599079 1.5166078-5.8994533s-.5501115-4.1506053-1.5166078-5.8994533zm-4.7982246-4.79822459-4.9217466 4.92174663c-1.748848-.96649637-3.7599079-1.51660786-5.8994533-1.51660786-6.7457637 0-12.21428571 5.46852201-12.21428571 12.21428571s5.46852201 12.2142857 12.21428571 12.2142857c2.1395454 0 4.1506053-.5501115 5.8994533-1.5166078l4.9217466 4.9217466c-3.0707265 2.1314244-6.8000858 3.3805755-10.8211999 3.3805755-10.49341025 0-19-8.5065898-19-19 0-10.49341025 8.50658975-19 19-19 4.0211141 0 7.7504734 1.24915112 10.8211999 3.38057552z"
fill="#7fadf2"
id="path2"
style="fill:#ffffff;fill-opacity:1" />
<path
d="m34.6194245 8.17880011c2.1314244 3.07072649 3.3805755 6.80008579 3.3805755 10.82119989s-1.2491511 7.7504734-3.3805755 10.8211999l-4.9217466-4.9217466c.9664963-1.748848 1.5166078-3.7599079 1.5166078-5.8994533s-.5501115-4.1506053-1.5166078-5.8994533z"
fill="#b8d3f4"
id="path4"
style="fill:#ffffff;fill-opacity:1" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

View file

@ -0,0 +1,4 @@
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M505 442.7 405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"/>
<path d="M236.475 116.292c-30.447 13.753-37.74 53.708-14.117 77.332 23.624 23.624 63.579 16.33 77.332-14.117 29.331 95.273-88.453 166.118-158.893 95.678s.406-188.224 95.678-158.893z" style="stroke-width:23.8909"/>
</svg>

After

Width:  |  Height:  |  Size: 666 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-settings"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>

After

Width:  |  Height:  |  Size: 1,011 B

View file

@ -0,0 +1,7 @@
<svg fill="#fff" viewBox="0 0 20 20" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
<path d="M10 12.5c-5.92 0-9 3.5-9 5.5v1h18v-1c0-2-3.08-5.5-9-5.5z"/>
<circle cx="10" cy="6" r="5"/>
<g style="fill:#fff">
<path d="M47.788 25.736a19.5 19.5 0 0 1 0 21.4m7.1-28.5a30 30 0 0 1 0 35.6m6.5-42.1a38.8 38.8 0 0 1 0 48.6m105.752-35a19.5 19.5 0 0 0 0 21.4m-7.1-28.5a30 30 0 0 0 0 35.6m-6.5-42.1a38.8 38.8 0 0 0 0 48.6" style="fill:none;stroke:#fff;stroke-width:5;stroke-linecap:round" transform="translate(-7.276 -.048) scale(.16067)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 562 B

View file

@ -1,49 +1,41 @@
<?xml version="1.0" encoding="iso-8859-1"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <svg
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> width="16"
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" height="16"
viewBox="0 0 112.197 112.197" style="enable-background:new 0 0 112.197 112.197;" xml:space="preserve"> fill="currentColor"
<g> class="bi bi-twitter"
<circle style="fill:#55ACEE;" cx="56.099" cy="56.098" r="56.098"/> viewBox="0 0 16 16"
<g> version="1.1"
<path style="fill:#F1F2F2;" d="M90.461,40.316c-2.404,1.066-4.99,1.787-7.702,2.109c2.769-1.659,4.894-4.284,5.897-7.417 id="svg4"
c-2.591,1.537-5.462,2.652-8.515,3.253c-2.446-2.605-5.931-4.233-9.79-4.233c-7.404,0-13.409,6.005-13.409,13.409 sodipodi:docname="twitter.svg"
c0,1.051,0.119,2.074,0.349,3.056c-11.144-0.559-21.025-5.897-27.639-14.012c-1.154,1.98-1.816,4.285-1.816,6.742 inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
c0,4.651,2.369,8.757,5.965,11.161c-2.197-0.069-4.266-0.672-6.073-1.679c-0.001,0.057-0.001,0.114-0.001,0.17 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
c0,6.497,4.624,11.916,10.757,13.147c-1.124,0.308-2.311,0.471-3.532,0.471c-0.866,0-1.705-0.083-2.523-0.239 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
c1.706,5.326,6.657,9.203,12.526,9.312c-4.59,3.597-10.371,5.74-16.655,5.74c-1.08,0-2.15-0.063-3.197-0.188 xmlns="http://www.w3.org/2000/svg"
c5.931,3.806,12.981,6.025,20.553,6.025c24.664,0,38.152-20.432,38.152-38.153c0-0.581-0.013-1.16-0.039-1.734 xmlns:svg="http://www.w3.org/2000/svg">
C86.391,45.366,88.664,43.005,90.461,40.316L90.461,40.316z"/> <defs
</g> id="defs8" />
</g> <sodipodi:namedview
<g> id="namedview6"
</g> pagecolor="#ffffff"
<g> bordercolor="#666666"
</g> borderopacity="1.0"
<g> inkscape:showpageshadow="2"
</g> inkscape:pageopacity="0.0"
<g> inkscape:pagecheckerboard="0"
</g> inkscape:deskcolor="#d1d1d1"
<g> showgrid="false"
</g> inkscape:zoom="51.8125"
<g> inkscape:cx="7.5946924"
</g> inkscape:cy="8.0096502"
<g> inkscape:window-width="1920"
</g> inkscape:window-height="1009"
<g> inkscape:window-x="-8"
</g> inkscape:window-y="-8"
<g> inkscape:window-maximized="1"
</g> inkscape:current-layer="svg4" />
<g> <path
</g> d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
<g> id="path2"
</g> style="fill:#ffffff;fill-opacity:1" />
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

View file

@ -11,6 +11,7 @@ const CiderAudio = {
intelliGainComp: null, intelliGainComp: null,
atmosphereRealizer2: null, atmosphereRealizer2: null,
atmosphereRealizer1: null, atmosphereRealizer1: null,
opportunisticCorrection: null
}, },
ccON: false, ccON: false,
mediaRecorder: null, mediaRecorder: null,
@ -40,6 +41,7 @@ const CiderAudio = {
intelliGainComp: null, intelliGainComp: null,
atmosphereRealizer2: null, atmosphereRealizer2: null,
atmosphereRealizer1: null, atmosphereRealizer1: null,
opportunisticCorrection: null,
} }
} catch (e) { } } catch (e) { }
CiderAudio.source.connect(CiderAudio.context.destination); CiderAudio.source.connect(CiderAudio.context.destination);
@ -47,7 +49,8 @@ const CiderAudio = {
}, },
connectContext: function (mediaElem) { connectContext: function (mediaElem) {
if (!CiderAudio.context) { if (!CiderAudio.context) {
CiderAudio.context = new window.AudioContext({ sampleRate: 96000 }); // Don't ever remove the sample rate arg. Ask Maikiwi. CiderAudio.context = new window.AudioContext({ sampleRate: 96000, latencyHint: "playback"}); // Don't ever remove the sample rate arg. Ask Maikiwi.
app.lyricOffset = CiderAudio.context.baseLatency
} }
if (!CiderAudio.source) { if (!CiderAudio.source) {
CiderAudio.source = CiderAudio.context.createMediaElementSource(mediaElem); CiderAudio.source = CiderAudio.context.createMediaElementSource(mediaElem);
@ -80,6 +83,7 @@ const CiderAudio = {
} }
} catch (e) { } catch (e) {
console.debug("[Cider][MaikiwiSoundCheck] normalizer func err: " + e)
} }
}, },
normalizerOff: function () { normalizerOff: function () {
@ -225,6 +229,14 @@ const CiderAudio = {
"description": "8500", "description": "8500",
} }
], ],
opportunisticCorrectionProfiles: [
{
"id": "CHU",
"file": './cideraudio/impulses/MoondropCHU_Cider.wav',
"name": "Moondrop CHU Specific",
"description": "",
}
],
spatial_ninf: function () { spatial_ninf: function () {
CiderAudio.audioNodes.spatialNode = null; CiderAudio.audioNodes.spatialNode = null;
CiderAudio.audioNodes.spatialNode = CiderAudio.context.createConvolver(); CiderAudio.audioNodes.spatialNode = CiderAudio.context.createConvolver();
@ -385,7 +397,7 @@ const CiderAudio = {
if (this._isBufferFull()) { if (this._isBufferFull()) {
this._flush(); this._flush();
} }
let dataLength = audioRawData[0].length; let dataLength = audioRawData[0]?.length ?? 0;
for (let idx=0; idx<dataLength; idx++) { for (let idx=0; idx<dataLength; idx++) {
for (let channel=0; channel < numberOfChannels; channel++) { for (let channel=0; channel < numberOfChannels; channel++) {
let value = audioRawData[channel][idx]; let value = audioRawData[channel][idx];
@ -466,7 +478,7 @@ const CiderAudio = {
} }
} }
CiderAudio.audioNodes.recorderNode.parameters.get('isRecording').setValueAtTime(1, CiderAudio.context.currentTime); CiderAudio.audioNodes.recorderNode.parameters.get('isRecording').setValueAtTime(1, CiderAudio.context.currentTime);
CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.recorderNode); CiderAudio.audioNodes.intelliGainComp.connect(CiderAudio.audioNodes.recorderNode);
}); });
clearInterval(searchInt); clearInterval(searchInt);
@ -488,7 +500,7 @@ const CiderAudio = {
// CiderAudio.ccON = false; // CiderAudio.ccON = false;
} }
}, },
atmosphereRealizer2_n5: function (status, destination) { atmosphereRealizer2_n6: function (status, destination) {
if (status === true) { if (status === true) {
CiderAudio.audioNodes.atmosphereRealizer2 = CiderAudio.context.createConvolver(); CiderAudio.audioNodes.atmosphereRealizer2 = CiderAudio.context.createConvolver();
CiderAudio.audioNodes.atmosphereRealizer2.normalize = false; CiderAudio.audioNodes.atmosphereRealizer2.normalize = false;
@ -506,40 +518,46 @@ const CiderAudio = {
switch (destination) { switch (destination) {
case "spatial": case "spatial":
try { CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.audioNodes.spatialNode); console.debug("[Cider][Audio] atmosphereRealizer2_n5 -> Spatial");} catch (e) { } try { CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.audioNodes.spatialNode); console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> Spatial");} catch (e) { }
break; break;
case "n5": case "n6":
try { try {
CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.audioNodes.atmosphereRealizer2); CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] atmosphereRealizer2_n5 -> atmosphereRealizer2"); console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n5':
try {
CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> atmosphereRealizer1");
} catch (e) { } } catch (e) { }
break; break;
case 'n4': case 'n4':
try { try {
CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.audioNodes.atmosphereRealizer1); CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] atmosphereRealizer2_n5 -> atmosphereRealizer1"); console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> vibrantbassNode");
} catch (e) { } } catch (e) { }
break; break;
case 'n3': case 'n3':
try { try {
CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.audioNodes.vibrantbassNode[0]); CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.audioNodes.audioBands[0]);
console.debug("[Cider][Audio] atmosphereRealizer2_n5 -> vibrantbassNode"); console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> audioBands");
} catch (e) { } } catch (e) { }
break; break;
case 'n2': case 'n2':
try { try {
CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.audioNodes.audioBands[0]); CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] atmosphereRealizer2_n5 -> audioBands"); console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> opportunisticCorrection");
} catch (e) { } } catch (e) { }
break; break;
case 'n1': case 'n1':
try { try {
CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.audioNodes.llpw[0]); CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.audioNodes.llpw[0]);
console.debug("[Cider][Audio] atmosphereRealizer2_n5 -> llpw"); console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> llpw");
} catch (e) { } } catch (e) { }
break; break;
case 'n0': case 'n0':
try { CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.context.destination); console.debug("[Cider][Audio] atmosphereRealizer2_n5 -> destination");} catch (e) { } try { CiderAudio.audioNodes.atmosphereRealizer2.connect(CiderAudio.context.destination); console.debug("[Cider][Audio] atmosphereRealizer2_n6 -> destination");} catch (e) { }
break; break;
} }
@ -547,7 +565,7 @@ const CiderAudio = {
} }
}, },
atmosphereRealizer1_n4: function (status, destination) { atmosphereRealizer1_n5: function (status, destination) {
if (status === true) { if (status === true) {
CiderAudio.audioNodes.atmosphereRealizer1 = CiderAudio.context.createConvolver(); CiderAudio.audioNodes.atmosphereRealizer1 = CiderAudio.context.createConvolver();
CiderAudio.audioNodes.atmosphereRealizer1.normalize = false; CiderAudio.audioNodes.atmosphereRealizer1.normalize = false;
@ -565,40 +583,46 @@ const CiderAudio = {
switch (destination) { switch (destination) {
case "spatial": case "spatial":
try { CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.audioNodes.spatialNode); console.debug("[Cider][Audio] atmosphereRealizer1_n4 -> Spatial");} catch (e) { } try { CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.audioNodes.spatialNode); console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> Spatial");} catch (e) { }
break; break;
case "n5": case "n6":
try { try {
CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.audioNodes.atmosphereRealizer2); CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] atmosphereRealizer1_n4 -> atmosphereRealizer2"); console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n5':
try {
CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> atmosphereRealizer1");
} catch (e) { } } catch (e) { }
break; break;
case 'n4': case 'n4':
try { try {
CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.audioNodes.atmosphereRealizer1); CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] atmosphereRealizer1_n4 -> atmosphereRealizer1"); console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> vibrantbassNode");
} catch (e) { } } catch (e) { }
break; break;
case 'n3': case 'n3':
try { try {
CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.audioNodes.vibrantbassNode[0]); CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.audioNodes.audioBands[0]);
console.debug("[Cider][Audio] atmosphereRealizer1_n4 -> vibrantbassNode"); console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> audioBands");
} catch (e) { } } catch (e) { }
break; break;
case 'n2': case 'n2':
try { try {
CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.audioNodes.audioBands[0]); CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] atmosphereRealizer1_n4 -> audioBands"); console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> opportunisticCorrection");
} catch (e) { } } catch (e) { }
break; break;
case 'n1': case 'n1':
try { try {
CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.audioNodes.llpw[0]); CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.audioNodes.llpw[0]);
console.debug("[Cider][Audio] atmosphereRealizer1_n4 -> llpw"); console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> llpw");
} catch (e) { } } catch (e) { }
break; break;
case 'n0': case 'n0':
try { CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.context.destination); console.debug("[Cider][Audio] atmosphereRealizer1_n4 -> destination");} catch (e) { } try { CiderAudio.audioNodes.atmosphereRealizer1.connect(CiderAudio.context.destination); console.debug("[Cider][Audio] atmosphereRealizer1_n5 -> destination");} catch (e) { }
break; break;
} }
@ -606,6 +630,64 @@ const CiderAudio = {
} }
}, },
opportunisticCorrection_n2: function (status, destination) {
if (status === true) {
CiderAudio.audioNodes.opportunisticCorrection = CiderAudio.context.createConvolver();
CiderAudio.audioNodes.opportunisticCorrection.normalize = false;
let opportunisticCorrectionProfile = CiderAudio.opportunisticCorrectionProfiles.find(function (profile) {
return profile.id === app.cfg.audio.maikiwiAudio.opportunisticCorrection_state;
});
if (opportunisticCorrectionProfile === undefined) {
opportunisticCorrectionProfile = CiderAudio.opportunisticCorrectionProfiles[0];
}
fetch(opportunisticCorrectionProfile.file).then(async (impulseData) => {
let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudio.audioNodes.opportunisticCorrection.buffer = await CiderAudio.context.decodeAudioData(bufferedImpulse);
});
switch (destination) {
case "spatial":
try { CiderAudio.audioNodes.opportunisticCorrection.connect(CiderAudio.audioNodes.spatialNode); console.debug("[Cider][Audio] opportunisticCorrection_n2 -> Spatial");} catch (e) { }
break;
case "n6":
try {
CiderAudio.audioNodes.opportunisticCorrection.connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] opportunisticCorrection_n2 -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n5':
try {
CiderAudio.audioNodes.opportunisticCorrection.connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] opportunisticCorrection_n2 -> atmosphereRealizer1");
} catch (e) { }
break;
case 'n4':
try { CiderAudio.audioNodes.opportunisticCorrection.connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] opportunisticCorrection_n2 -> vibrantbassNode");} catch (e) { }
break;
case 'n3':
try { CiderAudio.audioNodes.opportunisticCorrection.connect(CiderAudio.audioNodes.audioBands[0]); console.debug("[Cider][Audio] opportunisticCorrection_n2 -> audioBands");} catch (e) { }
break;
case 'n2':
try {
CiderAudio.audioNodes.opportunisticCorrection.connect(CiderAudio.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] opportunisticCorrection_n2 -> opportunisticCorrection");
} catch (e) { }
break;
case 'n1':
try {
CiderAudio.audioNodes.opportunisticCorrection.connect(CiderAudio.audioNodes.opportunisticCorrection[0]);
console.debug("[Cider][Audio] opportunisticCorrection_n2 -> opportunisticCorrection");
} catch (e) { }
break;
case 'n0':
try { CiderAudio.audioNodes.opportunisticCorrection.connect(CiderAudio.context.destination); console.debug("[Cider][Audio] opportunisticCorrection_n2 -> destination");} catch (e) { }
break;
}
}
},
llpw_n1: function (status, destination) { llpw_n1: function (status, destination) {
if (status === true) { 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_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];
@ -639,18 +721,12 @@ const CiderAudio = {
let bufferedImpulse = await impulseData.arrayBuffer(); let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudio.audioNodes.llpw[0].buffer = await CiderAudio.context.decodeAudioData(bufferedImpulse); CiderAudio.audioNodes.llpw[0].buffer = await CiderAudio.context.decodeAudioData(bufferedImpulse);
}); });
console.debug("[Cider][Audio] CAP Adaptive - 256kbps_2_48k"); console.debug("[Cider][Audio] CAP Adaptive - 256kbps");
break; break;
default: default:
CiderAudio.audioNodes.llpw[0] = CiderAudio.context.createConvolver(); CiderAudio.audioNodes.llpw[0].normalize = false; CiderAudio.audioNodes.llpw[0] = CiderAudio.context.createGain(); CiderAudio.audioNodes.llpw[0].gain.value = 1
CiderAudio.audioNodes.llpw[1] = CiderAudio.context.createGain(); CiderAudio.audioNodes.llpw[1].gain.value = 2.37; // Post Gain Compensation console.debug("[Cider][Audio] CAP Disabled (Passthrough) : Non-Lossy Bitrate.");
CiderAudio.audioNodes.llpw[0].connect(CiderAudio.audioNodes.llpw[1]);
fetch('./cideraudio/impulses/CAP_256_FINAL_48k.wav').then(async (impulseData) => {
let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudio.audioNodes.llpw[0].buffer = await CiderAudio.context.decodeAudioData(bufferedImpulse);
});
console.debug("[Cider][Audio] CAP Adaptive - CONFIG FALLBACK - 256kbps_2_48k");
break; break;
} }
@ -666,41 +742,6 @@ const CiderAudio = {
console.debug("[Cider][Audio] CAP Adaptive - (Error Fallback) 256kbps"); console.debug("[Cider][Audio] CAP Adaptive - (Error Fallback) 256kbps");
} }
switch (destination) {
case "spatial":
try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.spatialNode); console.debug("[Cider][Audio] llpw_n1 -> Spatial");} catch (e) { }
break;
case "n5":
try {
CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n4':
try {
CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer1");
} catch (e) { }
break;
case 'n3':
try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] llpw_n1 -> vibrantbassNode");} catch (e) { }
break;
case 'n2':
try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.audioBands[0]); console.debug("[Cider][Audio] llpw_n1 -> audioBands");} catch (e) { }
break;
case 'n1':
try {
CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.llpw[0]);
console.debug("[Cider][Audio] llpw_n1 -> llpw");
} catch (e) { }
break;
case 'n0':
try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.context.destination); console.debug("[Cider][Audio] llpw_n1 -> destination");} catch (e) { }
break;
}
break; break;
case "MAIKIWI_LEGACY": case "MAIKIWI_LEGACY":
CiderAudio.audioNodes.llpw[0] = CiderAudio.context.createConvolver(); CiderAudio.audioNodes.llpw[0] = CiderAudio.context.createConvolver();
@ -709,41 +750,6 @@ const CiderAudio = {
let bufferedImpulse = await impulseData.arrayBuffer(); let bufferedImpulse = await impulseData.arrayBuffer();
CiderAudio.audioNodes.llpw[0].buffer = await CiderAudio.context.decodeAudioData(bufferedImpulse); CiderAudio.audioNodes.llpw[0].buffer = await CiderAudio.context.decodeAudioData(bufferedImpulse);
}); });
switch (destination) {
case "spatial":
try { CiderAudio.audioNodes.llpw[0].connect(CiderAudio.audioNodes.spatialNode); console.debug("[Cider][Audio] llpw_n1 -> Spatial");} catch (e) { }
break;
case "n5":
try {
CiderAudio.audioNodes.llpw[0].connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n4':
try {
CiderAudio.audioNodes.llpw[0].connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer1");
} catch (e) { }
break;
case 'n1':
try {
CiderAudio.audioNodes.llpw[0].connect(CiderAudio.audioNodes.llpw[0]);
console.debug("[Cider][Audio] llpw_n1 -> llpw");
} catch (e) { }
break;
case 'n3':
try { CiderAudio.audioNodes.llpw[0].connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] llpw_n1 -> vibrantbassNode");} catch (e) { }
break;
case 'n2':
try { CiderAudio.audioNodes.llpw[0].connect(CiderAudio.audioNodes.audioBands[0]); console.debug("[Cider][Audio] llpw_n1 -> audioBands");} catch (e) { }
break;
case 'n0':
try { CiderAudio.audioNodes.llpw[0].connect(CiderAudio.context.destination); console.debug("[Cider][Audio] llpw_n1 -> destination");} catch (e) { }
break;
}
console.debug("[Cider][Audio] CAP - Maikiwi Signature Mode"); console.debug("[Cider][Audio] CAP - Maikiwi Signature Mode");
break; break;
case "NATURAL": case "NATURAL":
@ -754,41 +760,6 @@ const CiderAudio = {
CiderAudio.audioNodes.llpw[0].buffer = await CiderAudio.context.decodeAudioData(bufferedImpulse); CiderAudio.audioNodes.llpw[0].buffer = await CiderAudio.context.decodeAudioData(bufferedImpulse);
}); });
switch (destination) {
case "spatial":
try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.spatialNode); console.debug("[Cider][Audio] llpw_n1 -> Spatial");} catch (e) { }
break;
case "n5":
try {
CiderAudio.audioNodes.llpw[0].connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n4':
try {
CiderAudio.audioNodes.llpw[0].connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer1");
} catch (e) { }
break;
case 'n1':
try {
CiderAudio.audioNodes.llpw[0].connect(CiderAudio.audioNodes.llpw[0]);
console.debug("[Cider][Audio] llpw_n1 -> llpw");
} catch (e) { }
break;
case 'n3':
try { CiderAudio.audioNodes.llpw[0].connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] llpw_n1 -> vibrantbassNode");} catch (e) { }
break;
case 'n2':
try { CiderAudio.audioNodes.llpw[0].connect(CiderAudio.audioNodes.audioBands[0]); console.debug("[Cider][Audio] llpw_n1 -> audioBands");} catch (e) { }
break;
case 'n0':
try { CiderAudio.audioNodes.llpw[0].connect(CiderAudio.context.destination); console.debug("[Cider][Audio] llpw_n1 -> destination");} catch (e) { }
break;
}
console.debug("[Cider][Audio] CAP - Natural Mode"); console.debug("[Cider][Audio] CAP - Natural Mode");
break; break;
@ -803,40 +774,6 @@ const CiderAudio = {
for (let i = 1; i < LLPW_FREQUENCIES.length; i ++) { for (let i = 1; i < LLPW_FREQUENCIES.length; i ++) {
CiderAudio.audioNodes.llpw[i-1].connect(CiderAudio.audioNodes.llpw[i]); CiderAudio.audioNodes.llpw[i-1].connect(CiderAudio.audioNodes.llpw[i]);
} }
switch (destination) {
case "spatial":
try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.spatialNode); console.debug("[Cider][Audio] llpw_n1 -> Spatial");} catch (e) { }
break;
case "n5":
try {
CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n4':
try {
CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer1");
} catch (e) { }
break;
case 'n1':
try {
CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.llpw[0]);
console.debug("[Cider][Audio] llpw_n1 -> llpw");
} catch (e) { }
break;
case 'n3':
try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] llpw_n1 -> vibrantbassNode");} catch (e) { }
break;
case 'n2':
try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.audioBands[0]); console.debug("[Cider][Audio] llpw_n1 -> audioBands");} catch (e) { }
break;
case 'n0':
try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.context.destination); console.debug("[Cider][Audio] llpw_n1 -> destination");} catch (e) { }
break;
}
console.debug("[Cider][Audio] CAP - Legacy Mode"); console.debug("[Cider][Audio] CAP - Legacy Mode");
break; break;
@ -851,46 +788,53 @@ const CiderAudio = {
}); });
app.cfg.audio.maikiwiAudio.ciderPPE_value = "MAIKIWI"; app.cfg.audio.maikiwiAudio.ciderPPE_value = "MAIKIWI";
console.debug("[Cider][Audio] CAP - Maikiwi Adaptive Mode (Defaulted from broki config)");
break;
}
switch (destination) { switch (destination) {
case "spatial": case "spatial":
try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.spatialNode); console.debug("[Cider][Audio] llpw_n1 -> Spatial");} catch (e) { } try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.spatialNode); console.debug("[Cider][Audio] llpw_n1 -> Spatial");} catch (e) { }
break; break;
case "n5": case "n6":
try { try {
CiderAudio.audioNodes.llpw[1].connect(CiderAudio.audioNodes.atmosphereRealizer2); CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer2"); console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer2");
} catch (e) { } } catch (e) { }
break; break;
case 'n4': case 'n5':
try { try {
CiderAudio.audioNodes.llpw[1].connect(CiderAudio.audioNodes.atmosphereRealizer1); CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer1"); console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer1");
} catch (e) { } } catch (e) { }
break; break;
case 'n4':
try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] llpw_n1 -> vibrantbassNode");} catch (e) { }
break;
case 'n3':
try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.audioBands[0]); console.debug("[Cider][Audio] llpw_n1 -> audioBands");} catch (e) { }
break;
case 'n2':
try {
CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] llpw_n1 -> opportunisticCorrection");
} catch (e) { }
break;
case 'n1': case 'n1':
try { try {
CiderAudio.audioNodes.llpw[1].connect(CiderAudio.audioNodes.llpw[0]); CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.llpw[0]);
console.debug("[Cider][Audio] llpw_n1 -> llpw"); console.debug("[Cider][Audio] llpw_n1 -> llpw");
} catch (e) { } } catch (e) { }
break; break;
case 'n3':
try { CiderAudio.audioNodes.llpw[1].connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] llpw_n1 -> vibrantbassNode");} catch (e) { }
break;
case 'n2':
try { CiderAudio.audioNodes.llpw[1].connect(CiderAudio.audioNodes.audioBands[0]); console.debug("[Cider][Audio] llpw_n1 -> audioBands");} catch (e) { }
break;
case 'n0': case 'n0':
try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.context.destination); console.debug("[Cider][Audio] llpw_n1 -> destination");} catch (e) { } try { CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.context.destination); console.debug("[Cider][Audio] llpw_n1 -> destination");} catch (e) { }
break; break;
} }
console.debug("[Cider][Audio] CAP - Maikiwi Adaptive Mode (Defaulted from broki config)");
break;
}
} }
}, },
vibrantbass_n3: function (status, destination) { vibrantbass_n4: function (status, destination) {
if (status === true) { if (status === true) {
let VIBRANTBASSBANDS = app.cfg.audio.maikiwiAudio.vibrantBass.frequencies; let VIBRANTBASSBANDS = app.cfg.audio.maikiwiAudio.vibrantBass.frequencies;
let VIBRANTBASSGAIN = app.cfg.audio.maikiwiAudio.vibrantBass.gain; let VIBRANTBASSGAIN = app.cfg.audio.maikiwiAudio.vibrantBass.gain;
@ -911,41 +855,47 @@ const CiderAudio = {
switch (destination) { switch (destination) {
case "spatial": case "spatial":
try { CiderAudio.audioNodes.vibrantbassNode[0].connect(CiderAudio.audioNodes.spatialNode); console.debug("[Cider][Audio] vibrantbass_n3 -> Spatial");} catch (e) { } try { CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.spatialNode); console.debug("[Cider][Audio] vibrantbass_n4 -> Spatial");} catch (e) { }
break; break;
case "n5": case "n6":
try { try {
CiderAudio.audioNodes.vibrantbassNode[0].connect(CiderAudio.audioNodes.atmosphereRealizer2); CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] vibrantbass_n3 -> atmosphereRealizer2"); console.debug("[Cider][Audio] vibrantbass_n4 -> atmosphereRealizer2");
} catch (e) { }
break;
case 'n5':
try {
CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] vibrantbass_n4 -> atmosphereRealizer1");
} catch (e) { } } catch (e) { }
break; break;
case 'n4': case 'n4':
try { try {
CiderAudio.audioNodes.vibrantbassNode[0].connect(CiderAudio.audioNodes.atmosphereRealizer1); CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] vibrantbass_n3 -> atmosphereRealizer1"); console.debug("[Cider][Audio] vibrantbass_n4 -> vibrantbassNode");
} catch (e) { } } catch (e) { }
break; break;
case 'n3': case 'n3':
try { try {
CiderAudio.audioNodes.vibrantbassNode[0].connect(CiderAudio.audioNodes.vibrantbassNode[0]); CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.audioBands[0]);
console.debug("[Cider][Audio] vibrantbass_n3 -> vibrantbassNode"); console.debug("[Cider][Audio] vibrantbass_n4 -> audioBands");
} catch (e) { } } catch (e) { }
break; break;
case 'n2': case 'n2':
try { try {
CiderAudio.audioNodes.vibrantbassNode[0].connect(CiderAudio.audioNodes.audioBands[0]); CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] vibrantbass_n3 -> audioBands"); console.debug("[Cider][Audio] vibrantbass_n4 -> opportunisticCorrection");
} catch (e) { } } catch (e) { }
break; break;
case 'n1': case 'n1':
try { try {
CiderAudio.audioNodes.vibrantbassNode[0].connect(CiderAudio.audioNodes.llpw[0]); CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.llpw[0]);
console.debug("[Cider][Audio] vibrantbass_n3 -> llpw"); console.debug("[Cider][Audio] vibrantbass_n4 -> llpw");
} catch (e) { } } catch (e) { }
break; break;
case 'n0': case 'n0':
try { CiderAudio.audioNodes.vibrantbassNode[0].connect(CiderAudio.context.destination); console.debug("[Cider][Audio] vibrantbass_n3 -> destination");} catch (e) { } try { CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.context.destination); console.debug("[Cider][Audio] vibrantbass_n4 -> destination");} catch (e) { }
break; break;
} }
} }
@ -958,15 +908,17 @@ const CiderAudio = {
try { for (var i of CiderAudio.audioNodes.llpw) { i.disconnect(); } CiderAudio.audioNodes.llpw = null } catch (e) { } try { for (var i of CiderAudio.audioNodes.llpw) { i.disconnect(); } CiderAudio.audioNodes.llpw = null } catch (e) { }
try { for (var i of CiderAudio.audioNodes.vibrantbassNode) { i.disconnect(); } CiderAudio.audioNodes.vibrantbassNode = null } catch (e) { } try { for (var i of CiderAudio.audioNodes.vibrantbassNode) { i.disconnect(); } CiderAudio.audioNodes.vibrantbassNode = null } catch (e) { }
try { for (var i of CiderAudio.audioNodes.audioBands) { i.disconnect(); } CiderAudio.audioNodes.vibrantbassNode = null} catch (e) { }; try { for (var i of CiderAudio.audioNodes.audioBands) { i.disconnect(); } CiderAudio.audioNodes.vibrantbassNode = null} catch (e) { };
try {CiderAudio.audioNodes.opportunisticCorrection.disconnect(); CiderAudio.audioNodes.opportunisticCorrection = null } catch (e) { };
console.debug("[Cider][Audio] Finished hierarchical unloading") console.debug("[Cider][Audio] Finished hierarchical unloading")
}, },
hierarchical_loading: async function () { hierarchical_loading: async function () {
const configMap = new Map([ const configMap = new Map([
['spatial', app.cfg.audio.maikiwiAudio.spatial === true], ['spatial', app.cfg.audio.maikiwiAudio.spatial === true],
['n5', app.cfg.audio.maikiwiAudio.atmosphereRealizer2 === true], ['n6', app.cfg.audio.maikiwiAudio.atmosphereRealizer2 === true],
['n4', app.cfg.audio.maikiwiAudio.atmosphereRealizer1 === true], ['n5', app.cfg.audio.maikiwiAudio.atmosphereRealizer1 === true],
['n3', app.cfg.audio.equalizer.vibrantBass != 0], ['n4', app.cfg.audio.equalizer.vibrantBass != 0],
['n2', Math.max(...app.cfg.audio.equalizer.gain) != 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] ['n1', app.cfg.audio.maikiwiAudio.ciderPPE === true]
]); ]);
@ -979,22 +931,26 @@ const CiderAudio = {
CiderAudio.spatial_ninf(); CiderAudio.spatial_ninf();
lastNode = 'spatial'; lastNode = 'spatial';
break; break;
case 'n6':
app.cfg.audio.normalization = true;
CiderAudio.atmosphereRealizer2_n6(true, lastNode);
lastNode = 'n6';
break;
case 'n5': case 'n5':
app.cfg.audio.normalization = true; app.cfg.audio.normalization = true;
CiderAudio.atmosphereRealizer2_n5(true, lastNode); CiderAudio.atmosphereRealizer1_n5(true, lastNode);
lastNode = 'n5'; lastNode = 'n5';
break; break;
case 'n4': case 'n4':
app.cfg.audio.normalization = true; CiderAudio.vibrantbass_n4(true, lastNode);
CiderAudio.atmosphereRealizer1_n4(true, lastNode);
lastNode = 'n4'; lastNode = 'n4';
break; break;
case 'n3': case 'n3':
CiderAudio.vibrantbass_n3(true, lastNode); CiderAudio.equalizer(true, lastNode);
lastNode = 'n3'; lastNode = 'n3';
break; break;
case 'n2': case 'n2':
CiderAudio.equalizer(true, lastNode); CiderAudio.opportunisticCorrection_n2(true, lastNode);
lastNode = 'n2'; lastNode = 'n2';
break; break;
case 'n1': case 'n1':
@ -1011,22 +967,28 @@ const CiderAudio = {
CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.spatialNode); CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.spatialNode);
console.debug("[Cider][Audio] gainNode -> Spatial"); console.debug("[Cider][Audio] gainNode -> Spatial");
break; break;
case 'n5': case 'n6':
CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.atmosphereRealizer2); CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] gainNode -> atmosphereRealizer2"); console.debug("[Cider][Audio] gainNode -> atmosphereRealizer2");
break; break;
case 'n4': case 'n5':
CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.atmosphereRealizer1); CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] gainNode -> atmosphereRealizer1"); console.debug("[Cider][Audio] gainNode -> atmosphereRealizer1");
break; break;
case 'n3': case 'n4':
CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.vibrantbassNode[0]); CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] gainNode -> vibrantbass"); console.debug("[Cider][Audio] gainNode -> vibrantbass");
break; break;
case 'n2': case 'n3':
CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.audioBands[0]); CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.audioBands[0]);
console.debug("[Cider][Audio] gainNode -> audioBands"); console.debug("[Cider][Audio] gainNode -> audioBands");
break;
case 'n2':
try {
CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] gainNode -> opportunisticCorrection");
} catch (e) { }
break; break;
case 'n1': case 'n1':
CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.llpw[0]); CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.llpw[0]);
@ -1045,7 +1007,7 @@ const CiderAudio = {
}, },
equalizer: function (status, destination) { // n2_1 equalizer: function (status, destination) { // n3_1
if (status === true) { if (status === true) {
let BANDS = app.cfg.audio.equalizer.frequencies; let BANDS = app.cfg.audio.equalizer.frequencies;
let GAIN = app.cfg.audio.equalizer.gain; let GAIN = app.cfg.audio.equalizer.gain;
@ -1066,41 +1028,47 @@ const CiderAudio = {
switch (destination) { switch (destination) {
case 'spatial': case 'spatial':
CiderAudio.audioNodes.audioBands[BANDS.length - 1].connect(CiderAudio.audioNodes.spatialNode); CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.spatialNode);
console.debug("[Cider][Audio] Equalizer -> Spatial"); console.debug("[Cider][Audio] Equalizer -> Spatial");
break; break;
case "n5": case "n6":
try { try {
CiderAudio.audioNodes.audioBands[BANDS.length - 1].connect(CiderAudio.audioNodes.atmosphereRealizer2); CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] Equalizer -> atmosphereRealizer2"); console.debug("[Cider][Audio] Equalizer -> atmosphereRealizer2");
} catch (e) { } } catch (e) { }
break; break;
case 'n5':
try {
CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] Equalizer -> atmosphereRealizer1");
} catch (e) { }
break;
case 'n4': case 'n4':
try { try {
CiderAudio.audioNodes.audioBands[BANDS.length - 1].connect(CiderAudio.audioNodes.atmosphereRealizer1); CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] Equalizer -> atmosphereRealizer1"); console.debug("[Cider][Audio] Equalizer -> vibrantbassNode");
} catch (e) { } } catch (e) { }
break; break;
case 'n3': case 'n3':
try { try {
CiderAudio.audioNodes.audioBands[BANDS.length - 1].connect(CiderAudio.audioNodes.vibrantbassNode[0]); CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.audioBands[0]);
console.debug("[Cider][Audio] Equalizer -> vibrantbassNode"); console.debug("[Cider][Audio] Equalizer -> audioBands");
} catch (e) { } } catch (e) { }
break; break;
case 'n2': case 'n2':
try { try {
CiderAudio.audioNodes.audioBands[BANDS.length - 1].connect(CiderAudio.audioNodes.audioBands[0]); CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] Equalizer -> audioBands"); console.debug("[Cider][Audio] Equalizer -> opportunisticCorrection");
} catch (e) { } } catch (e) { }
break; break;
case 'n1': case 'n1':
try { try {
CiderAudio.audioNodes.audioBands[BANDS.length - 1].connect(CiderAudio.audioNodes.llpw[0]); CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.llpw[0]);
console.debug("[Cider][Audio] Equalizer -> llpw"); console.debug("[Cider][Audio] Equalizer -> llpw");
} catch (e) { } } catch (e) { }
break; break;
case 'n0': case 'n0':
try { CiderAudio.audioNodes.audioBands[BANDS.length - 1].connect(CiderAudio.context.destination); console.debug("[Cider][Audio] Equalizer -> destination");} catch (e) { } try { CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.context.destination); console.debug("[Cider][Audio] Equalizer -> destination");} catch (e) { }
break; break;
} }

Binary file not shown.

View file

@ -1,5 +1,9 @@
var notyf = new Notyf(); var notyf = new Notyf();
function clamp(num, min, max) {
return Math.min(Math.max(num, min), max);
}
const MusicKitObjects = { const MusicKitObjects = {
LibraryPlaylist: function () { LibraryPlaylist: function () {
this.id = ""; this.id = "";
@ -52,75 +56,15 @@ Vue.component("animated-number", {
}, },
}); });
Vue.component("sidebar-library-item", {
template: "#sidebar-library-item",
props: {
name: {
type: String,
required: true,
},
page: {
type: String,
required: true,
},
svgIcon: {
type: String,
required: false,
default: "",
},
cdClick: {
type: Function,
required: false,
},
},
data: function () {
return {
app: app,
svgIconData: "",
};
},
async mounted() {
if (this.svgIcon) {
this.svgIconData = await this.app.getSvgIcon(this.svgIcon);
}
},
methods: {},
});
function fallbackinitMusicKit() {
const request = new XMLHttpRequest();
function loadAlternateKey() {
let parsedJson = JSON.parse(this.responseText);
MusicKit.configure({
developerToken: parsedJson.developerToken,
app: {
name: "Apple Music",
build: "1978.4.1",
version: "1.0",
},
sourceType: 24,
suppressErrorDialog: true,
});
setTimeout(() => {
app.init();
if (app.cfg.visual.window_background_style == "mica" && !app.isDev) {
app.spawnMica();
}
}, 1000);
}
request.addEventListener("load", loadAlternateKey);
request.open(
"GET",
"https://raw.githubusercontent.com/lujjjh/LitoMusic/main/token.json"
);
request.send();
}
function initMusicKit() { function initMusicKit() {
if(!this.responseText) {
console.log("Using stored token")
this.responseText = JSON.stringify({
token: localStorage.getItem("lastToken")
})
}
let parsedJson = JSON.parse(this.responseText); let parsedJson = JSON.parse(this.responseText);
localStorage.setItem("lastToken", parsedJson.token);
MusicKit.configure({ MusicKit.configure({
developerToken: parsedJson.token, developerToken: parsedJson.token,
app: { app: {
@ -150,8 +94,12 @@ function capiInit() {
request.timeout = 5000; request.timeout = 5000;
request.addEventListener("load", initMusicKit); request.addEventListener("load", initMusicKit);
request.onreadystatechange = function (aEvt) { request.onreadystatechange = function (aEvt) {
if (request.readyState == 4) { if (request.readyState == 4 && request.status != 200) {
if (request.status != 200) fallbackinitMusicKit(); if(localStorage.getItem("lastToken") != null) {
initMusicKit()
} else {
console.error(`Failed to load capi, cannot get token [${request.status}]`)
}
} }
}; };
request.open("GET", "https://api.cider.sh/v1/"); request.open("GET", "https://api.cider.sh/v1/");

View file

@ -239,6 +239,14 @@ input[type=range].md-slider::-webkit-slider-runnable-track {
width: auto; width: auto;
} }
@media only screen and (min-width: 1133px) and (max-width: 1233px) {
.about-page {
.row .col-auto {
display: none !important;
}
}
}
.col-1 { .col-1 {
flex: 0 0 auto; flex: 0 0 auto;
width: 8.33333333%; width: 8.33333333%;

View file

@ -2432,10 +2432,9 @@ fieldset:disabled .btn {
.nav-pills .nav-link { .nav-pills .nav-link {
background-color: transparent; background-color: transparent;
border: 0; border: 0;
border-radius: 50px; border-radius: 6px;
color: #eee; color: #eee;
-webkit-user-drag: none; -webkit-user-drag: none;
// transition: transform .35s var(--appleEase), background-color .35s var(--appleEase);
font-weight: 500; font-weight: 500;
margin: 0px 4px; margin: 0px 4px;
&:hover { &:hover {
@ -2447,7 +2446,7 @@ fieldset:disabled .btn {
.nav-pills .show > .nav-link { .nav-pills .show > .nav-link {
color: #fff; color: #fff;
background-color: var(--selected); background-color: var(--selected);
outline:2px solid var(--keyColor); // outline:2px solid var(--keyColor);
} }
.nav-fill > .nav-link, .nav-fill > .nav-link,
@ -2643,6 +2642,14 @@ fieldset:disabled .btn {
width: auto; width: auto;
} }
@media only screen and (min-width: 1133px) and (max-width: 1233px) {
.about-page {
.row .col-auto {
display: none !important;
}
}
}
.col-1 { .col-1 {
flex : 0 0 auto; flex : 0 0 auto;
width: 8.33333333%; width: 8.33333333%;

View file

@ -35,6 +35,11 @@
margin : 0px; margin : 0px;
height : 100%; height : 100%;
position : relative; position : relative;
white-space: nowrap;
._svg-icon {
flex: 0 0 auto;
}
&:before { &:before {
--dist : 1px; --dist : 1px;
@ -338,7 +343,9 @@
#app.twopanel .app-chrome:not(.chrome-bottom) .app-chrome--center .top-nav-group .app-sidebar-item { #app.twopanel .app-chrome:not(.chrome-bottom) .app-chrome--center .top-nav-group .app-sidebar-item {
min-width: 110px; min-width: 110px;
font-size: 0em; .sidebar-item-text {
display: none;
}
.sidebar-icon { .sidebar-icon {
margin: 0px; margin: 0px;
@ -353,7 +360,9 @@
#app.twopanel .app-chrome:not(.chrome-bottom) .app-chrome--center .top-nav-group .app-sidebar-item { #app.twopanel .app-chrome:not(.chrome-bottom) .app-chrome--center .top-nav-group .app-sidebar-item {
min-width: 60px; min-width: 60px;
font-size: 0em; .sidebar-item-text {
display: none;
}
.sidebar-icon { .sidebar-icon {
margin: 0px; margin: 0px;

View file

@ -162,6 +162,32 @@
align-self: center; align-self: center;
} }
.page-btn {
align-self: center;
height: 32px;
}
.page-btn img {
height: 100%;
align-self: center;
}
.md-ico-first {
content: url('./assets/angles-left.svg');
}
.md-ico-prev {
content: url('./assets/chevron-left.svg');
}
.md-ico-next {
content: url('./assets/chevron-right.svg');
}
.md-ico-last {
content: url('./assets/angles-right.svg');
}
.reload-btn { .reload-btn {
background: rgb(86 86 86 / 52%); background: rgb(86 86 86 / 52%);
border-radius: 100%; border-radius: 100%;
@ -958,11 +984,11 @@
/* mediaitem-square */ /* mediaitem-square */
.cd-mediaitem-square { .cd-mediaitem-square {
--transitionDuration: .25s; --transitionDuration: .5s;
--scaleRate: 1.25; --scaleRate: 1.25;
--scaleRateArtwork: 1; --scaleRateArtwork: 1;
width: 200px; width: calc(160px * var(--windowRelativeScale));
height: 200px; height: calc(200px * var(--windowRelativeScale));
display: inline-flex; display: inline-flex;
flex: 0 0 auto; flex: 0 0 auto;
flex-direction: column; flex-direction: column;
@ -970,14 +996,13 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-radius: 6px; border-radius: 6px;
transition: width var(--transitionDuration) linear, height var(--transitionDuration) linear;
.artwork-container { .artwork-container {
position: relative; position: relative;
.artwork { .artwork {
height: 150px; height: calc(140px * var(--windowRelativeScale));
width: 150px; width: calc(140px * var(--windowRelativeScale));
background: blue; background: blue;
border-radius: var(--mediaItemRadius); border-radius: var(--mediaItemRadius);
background: var(--artwork); background: var(--artwork);
@ -985,7 +1010,6 @@
flex: 0 0 auto; flex: 0 0 auto;
margin: 6px; margin: 6px;
cursor: pointer; cursor: pointer;
transition: width var(--transitionDuration) linear, height var(--transitionDuration) linear;
.mediaitem-artwork { .mediaitem-artwork {
box-shadow: unset; box-shadow: unset;
@ -1059,31 +1083,31 @@
} }
} }
&:not(.mediaitem-card):not(.mediaitem-brick):not(.mediaitem-video):not(.noscale) { // &:not(.mediaitem-card):not(.mediaitem-brick):not(.mediaitem-video):not(.noscale) {
@media (min-width: 1460px) { // @media (min-width: 1460px) {
--scaleRate: 1.1; // --scaleRate: 1.1;
--scaleRateArtwork: 0.9; // --scaleRateArtwork: 0.9;
width: calc(200px * var(--scaleRate)); // width: calc(200px * var(--scaleRate));
height: calc(200px * var(--scaleRate)); // height: calc(200px * var(--scaleRate));
.artwork-container > .artwork { // .artwork-container > .artwork {
width: calc(190px * var(--scaleRateArtwork)); // width: calc(190px * var(--scaleRateArtwork));
height: calc(190px * var(--scaleRateArtwork)); // height: calc(190px * var(--scaleRateArtwork));
} // }
} // }
@media (min-width: 1550px) { // @media (min-width: 1550px) {
--scaleRate: 1.25; // --scaleRate: 1.25;
--scaleRateArtwork: 1; // --scaleRateArtwork: 1;
width: calc(200px * var(--scaleRate)); // width: calc(200px * var(--scaleRate));
height: calc(200px * var(--scaleRate)); // height: calc(200px * var(--scaleRate));
.artwork-container > .artwork { // .artwork-container > .artwork {
width: calc(190px * var(--scaleRateArtwork)); // width: calc(190px * var(--scaleRateArtwork));
height: calc(190px * var(--scaleRateArtwork)); // height: calc(190px * var(--scaleRateArtwork));
} // }
} // }
} // }
.info-rect { .info-rect {
@ -1135,10 +1159,12 @@
&.mediaitem-video { &.mediaitem-video {
height: 200px; height: 200px;
width: 240px; width: 240px;
transition: width var(--transitionDuration) linear, height var(--transitionDuration) linear;
.artwork { .artwork {
height: 120px; height: 120px;
width: 212px; width: 212px;
transition: width var(--transitionDuration) linear, height var(--transitionDuration) linear;
} }
&:not(.noscale) { &:not(.noscale) {
@ -1171,10 +1197,12 @@
&.mediaitem-brick { &.mediaitem-brick {
height: 200px; height: 200px;
width: 240px; width: 240px;
transition: width var(--transitionDuration) linear, height var(--transitionDuration) linear;
.artwork { .artwork {
height: 123px; height: 123px;
width: 220px; width: 220px;
transition: width var(--transitionDuration) linear, height var(--transitionDuration) linear;
} }
&:not(.noscale) { &:not(.noscale) {
@ -1205,12 +1233,14 @@
} }
&.mediaitem-small { &.mediaitem-small {
width: 140px; width: calc(140px, var(--windowRelativeScale));
height: 180px; height: calc(180px, var(--windowRelativeScale));
transition: width var(--transitionDuration) linear, height var(--transitionDuration) linear;
.artwork { .artwork {
height: 128px; height: calc(128px, var(--windowRelativeScale));
width: 128px; width: calc(128px, var(--windowRelativeScale));
transition: width var(--transitionDuration) linear, height var(--transitionDuration) linear;
} }
} }
@ -1223,6 +1253,7 @@
position: relative; position: relative;
border-radius: calc(var(--mediaItemRadius) * 2); border-radius: calc(var(--mediaItemRadius) * 2);
box-shadow: var(--mediaItemShadow-ShadowSubtle); box-shadow: var(--mediaItemShadow-ShadowSubtle);
transition: width var(--transitionDuration) linear, height var(--transitionDuration) linear;
.artwork { .artwork {
width: 230px; width: 230px;
@ -1315,7 +1346,7 @@
} }
&:not(.noscale) { &:not(.noscale) {
@media (min-width: 1460px) { @media (min-width: 1200px) {
width: calc(230px * 1.1); width: calc(230px * 1.1);
height: calc(298px * 1.1); height: calc(298px * 1.1);
.artwork-container > .artwork { .artwork-container > .artwork {
@ -1324,7 +1355,7 @@
} }
} }
@media (min-width: 1550px) { @media (min-width: 1400px) {
width: calc(230px * 1.25); width: calc(230px * 1.25);
height: calc(298px * 1.25); height: calc(298px * 1.25);
.artwork-container > .artwork { .artwork-container > .artwork {
@ -1873,7 +1904,7 @@ input[type=checkbox][switch]:checked:active::before {
align-items: center; align-items: center;
color: white; color: white;
> svg { > ._svg-icon {
height: 16px; height: 16px;
width: 16px; width: 16px;
pointer-events: none; pointer-events: none;
@ -2064,6 +2095,7 @@ input[type=checkbox][switch]:checked:active::before {
font-size: 12px; font-size: 12px;
cursor: pointer; cursor: pointer;
flex: 0 0 32px; flex: 0 0 32px;
box-shadow: var(--ciderShadow-Generic);
&:hover { &:hover {
background: var(--selected); background: var(--selected);
@ -2168,12 +2200,20 @@ input[type=checkbox][switch]:checked:active::before {
} }
// fancy pills // fancy pills
.nav-pills { .fancy-pills {
.nav-pills {
position: relative; position: relative;
.nav-link { .nav-link {
transition: transform .3s var(--appleEase); transition: transform .3s var(--appleEase);
position: relative; position: relative;
background-color: transparent;
border: 0;
border-radius: 50px;
color: #eee;
-webkit-user-drag: none;
font-weight: 500;
margin: 0px 4px;
&:after { &:after {
@ -2270,6 +2310,7 @@ input[type=checkbox][switch]:checked:active::before {
z-index: 0; z-index: 0;
pointer-events: none; pointer-events: none;
} }
}
} }
.grouping-container { .grouping-container {

View file

@ -0,0 +1,604 @@
.fullscreen-view-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: black;
z-index: 99;
display: flex;
justify-content: center;
align-items: center;
opacity: 1;
}
.fullscreen-view {
width: 100%;
height: 100%;
background: black;
display: flex;
justify-content: center;
align-items: center;
--chromeHeight1: 70px;
.app-content-container {
width:100%;
height:100%;
#app-content {
width:100%;
height:100%;
.fs-search {
.search-input--icon {
width: 4em;
background-size: 40%;
background-position: center;
}
input {
padding-left: 2em;
font-size: 2em;
border-radius: var(--mediaItemRadius)
}
}
}
}
.fs-header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--chromeHeight1);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
.top-nav-group {
background : #1e1e1e99;
border : 1px solid lighten(@baseColor, 8);
border-radius: 12px;
display : flex;
height : 55px;
width: 90%;
backdrop-filter: var(--glassFilter);
.app-sidebar-item {
background-color: #1e1e1e00;
border-radius : 10px !important;
border : 0px;
min-width : 120px;
padding : 6px;
justify-content : center;
align-items : center;
margin : 0px;
height : 100%;
position : relative;
font-size: 1.1em;
font-weight: 500;
&:before {
--dist : 1px;
content : '';
position : absolute;
top : var(--dist);
left : var(--dist);
right : var(--dist);
bottom : var(--dist);
background-color: #fff;
opacity : 0;
border-radius : 10px;
transform : scale(0.5);
transition : transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
}
&:after {
display: none;
}
&:hover {
background-color: transparent;
&:before {
transition: transform 0.1s ease-in-out, opacity 0.1s ease-in-out;
opacity : .1;
transform : scale(1);
}
}
&.active {
background-color: transparent;
&:before {
opacity : .2;
transform: scale(1);
}
}
&.md-btn-primary {
box-shadow : 0px 0px 0px 1px lighten(@baseColor, @colorMixRate * 8);
background-color: lighten(@baseColor, @colorMixRate * 5);
z-index : 1;
}
}
}
}
.fs-row {
flex-grow: 0.5;
}
.playback-button--small.active {
background-color: rgb(200 200 200 / 25%);
}
.playback-button--small {
opacity: 0.7;
}
.right-col {
height: 50vh;
}
.bg-artwork-container {
display: block !important;
}
@media only screen and (max-width: 1121px) {
.display--large {
display: flex !important;
}
}
.display--large {
display: flex;
.slider {
width: 100%;
z-index: 1;
}
.input-container {
display: flex;
justify-content: center;
align-items: center;
width: 100%
}
.volume-button--small {
border-radius: 6px;
color: inherit;
background-size: 16px;
background-repeat: no-repeat;
background-position: center;
background-color: transparent;
height: 15px;
width: 30px;
border: 0px;
box-shadow: unset;
opacity: 0.70;
background-image: url("./assets/feather/volume-2.svg");
}
.volume-button--small:active {
transform: scale(0.9);
}
.volume-button--small.active {
background-image: url("./assets/feather/volume.svg");
}
input[type=range] {
-webkit-appearance: none;
height: 4px;
background: rgba(255, 255, 255, 0.4);
border-radius: 5px;
background-size: 70% 100%;
background-repeat: no-repeat;
&::-webkit-slider-thumb {
-webkit-appearance: none;
height: 14px;
width: 14px;
border-radius: 50%;
background: rgb(50 50 50);
cursor: default;
box-shadow: inset 0px 0px 0px 1px rgba(255, 255, 255, 0.4);
transition: all var(--appleTransition);
}
&::-webkit-slider-thumb:hover {
background-image: radial-gradient(var(--songProgressColor) 2px, transparent 3px, transparent 10px);
transform: scale(1.2);
}
&::-webkit-slider-thumb:active {
background-image: radial-gradient(var(--songProgressColor) 3px, transparent 4px, transparent 10px);
transform: scale(1);
}
&::-webkit-slider-runnable-track {
-webkit-appearance: none;
box-shadow: none;
border: none;
background: transparent;
}
}
}
.background {
position: absolute;
background-size: cover;
width: 100%;
height: 100%;
.bgArtworkMaterial {
position: absolute;
width: 100%;
height: 100%;
.bg-artwork-container {
z-index: unset;
}
.bg-artwork-container .bg-artwork {
filter: brightness(85%) saturate(95%) blur(180px) contrast(0.9) opacity(0.9);
}
}
}
.lyrics-col {
height: 62vh;
display: flex;
justify-content: center;
align-content: center;
width: 80%;
::-webkit-scrollbar-thumb {
box-shadow: unset;
}
&:hover ::-webkit-scrollbar-thumb {
box-shadow: inset 0px 0px 10px 10px rgb(200 200 200 / 50%);
}
.no-lyrics {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
}
.lyric-line {
font-size: 35px;
}
}
.queue-col {
width: 60vh;
height: 62vh;
.queue-title {
opacity: 0.6;
}
.queue-panel > * {
z-index: 3;
}
::-webkit-scrollbar-thumb {
box-shadow: unset;
}
&:hover ::-webkit-scrollbar-thumb {
box-shadow: inset 0px 0px 10px 10px rgb(200 200 200 / 50%);
}
}
.tab-toggles {
display: flex;
position: absolute;
bottom: 0;
right: 0;
width: 15vh;
height: 5vh;
justify-content: space-evenly;
.volume {
background-image: url("./assets/feathers/volume.svg");
padding: 0.5vh;
width: 2vh;
height: 2vh;
background-origin: content-box;
background-repeat: no-repeat;
}
.queue {
background-image: url("./assets/list.svg");
padding: 0.5vh;
width: 2.5vh;
height: 2.5vh;
background-origin: content-box;
background-repeat: no-repeat;
}
.lyrics {
background-image: url("./assets/quote-right.svg");
padding: 0.5vh;
width: 2.5vh;
height: 2.5vh;
background-origin: content-box;
background-repeat: no-repeat;
}
.active {
background-color: rgba(200, 200, 200, 0.7);
border-radius: 3px;
}
}
.artwork-col {
justify-content: center;
align-items: center;
display: flex;
flex-direction: column;
.artwork {
width: 50vh;
height: 50vh;
}
.controls-parents {
width: 50vh;
}
.app-playback-controls {
.song-artist, .song-name {
font-weight: 600;
text-align: center;
font-size: 0.9em;
height: 1.2em;
line-height: 0.9em;
overflow: hidden;
text-overflow: ellipsis;
max-width: 360px;
.song-name-normal {
height: inherit;
}
&.song-artist-marquee {
> marquee {
//margin-bottom: -3px;
}
}
}
.song-artist {
font-size: 0.875em;
font-weight: 400;
}
.song-name {
width: unset !important;
margin-top: 0.15vh;
display: -webkit-box;
line-height: 1.2;
text-overflow: ellipsis;
text-align: center;
}
}
.app-playback-controls .playback-info {
margin-top: 0.5vh;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
position: relative;
input[type="range"] {
width: 100%;
}
> div {
width: 100%;
text-align: center;
}
}
.app-playback-controls .song-progress {
@bgColor: transparent;
//height: 16px;
position: absolute;
bottom: -1.5vh;
left: 0px;
background: @bgColor;
.song-duration p {
font-weight: 400;
font-size: 10px;
height: 1.2em;
line-height: 1.3em;
overflow: hidden;
margin: 0 0 0 0.25em;
}
&:hover {
> input[type=range] {
&::-webkit-slider-thumb {
opacity: 1;
transform: scale(1);
z-index: 1;
}
}
}
input[type=range] {
appearance: none;
width: 100%;
height: 4px;
background-color: rgb(200 200 200 / 10%);
border-radius: 2px;
&::-webkit-slider-thumb {
opacity: 0;
transform: scale(0.5);
-webkit-appearance: none;
appearance: none;
width: 12px;
height: 12px;
border-radius: 100%;
background: var(--songProgressColor);
cursor: default;
transition: opacity .10s var(--appleEase), transform .10s var(--appleEase);
}
&::-moz-range-thumb {
width: 8px;
height: 8px;
border-radius: 100%;
background: var(--songProgressColor);
cursor: default;
}
}
}
.control-buttons {
margin-top: 2vh;
display: inline-flex;
width: 100%;
justify-content: center;
}
}
.cd-mediaitem-square {
font-size: 17px;
font-weight: 500;
}
.cd-mediaitem-square .artwork-container .artwork {
box-shadow: var(--mediaItemShadow-Shadow);
}
.cd-mediaitem-list-item {
height: 80px;
}
.cd-mediaitem-list-item .title {
font-size: 1.2em;
font-weight: 450;
}
.cd-mediaitem-list-item .subtitle {
font-size: 1.1em;
font-weight: 380;
}
.cd-mediaitem-list-item .duration {
font-size: 1.2em;
}
.cd-mediaitem-list-item .artwork {
width: 50px;
height: 50px;
}
.cd-btn-seeall {
font-size: 1.2em;
}
h1 {
font-size: 3em;
}
h3 {
font-size: 1.5rem;
}
.home-page .well.artistfeed-well {
height: 512px;
}
.header-text {
font-size: 3em;
height: 3em;
padding-left: 0.2em;
}
.grouping-container .grouping-btn {
font-size: 1.3em;
color: var(--textColor);
background-color: var(--sidebarColor);
font-weight: 600;
padding: 32px;
//box-shadow: var(--ciderShadow-Generic);
}
.content-inner.playlist-page {
display: flex;
flex-direction: row;
}
.playlist-page .playlist-display {
width: 100%;
max-width: 500px;
flex:1;
text-align: center;
.playlistInfo {
>.row {
justify-content: center;
}
}
.playlist-controls {
div {
width:100%;
}
}
}
.playlist-page .mediaContainer {
width: 30vh;
height: 30vh;
aspect-ratio: 1;
}
.playlist-page .playlist-display .playlistInfo .playlist-info {
gap: 16px;
margin-top: 40px;
}
.playlist-page .playlist-display .playlistInfo .playlist-hero {
transform: unset;
}
.artist-page .artist-header {
min-height: 60vh;
}
.artist-page .artist-image {
width: 20vh;
height: 20vh;
aspect-ratio: 1;
}
.artist-page.animated .artist-header {
min-height: 80vh;
}
.playlist-page .playlist-body {
flex: 1;
}
}

View file

@ -1,6 +1,6 @@
.notyf__toast { .notyf__toast {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
cursor : pointer; cursor: pointer;
} }
.notyf-info { .notyf-info {
@ -9,142 +9,145 @@
.tooltip-inner { .tooltip-inner {
background: #2f2f2f; background: #2f2f2f;
opacity : 1; opacity: 1;
border : 1px solid rgb(0 0 0 / 35%); border: 1px solid rgb(0 0 0 / 35%);
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.25); box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.25);
} }
.modal-fullscreen { .modal-fullscreen {
display : flex; display: flex;
justify-content: center; justify-content: center;
align-items : center; align-items: center;
position : fixed; position: fixed;
top : 0; top: 0;
left : 0; left: 0;
width : 100%; width: 100%;
height : 100%; height: 100%;
background : rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
z-index : 1000; z-index: 1000;
.modal-window { .modal-window {
background : #333; background: #333;
border-radius: 10px; border-radius: 10px;
box-shadow : var(--mediaItemShadow-Shadow); box-shadow: var(--mediaItemShadow-Shadow);
display : flex; display: flex;
flex-flow : column; flex-flow: column;
max-height : 500px; max-height: 500px;
max-width : 360px; max-width: 360px;
background : #121212; background: #121212;
width : 100%; width: 100%;
position : relative; position: relative;
&:after { &:after {
content : ""; content: "";
position : absolute; position: absolute;
top : 0; top: 0;
left : 0; left: 0;
width : 100%; width: 100%;
height : 100%; height: 100%;
pointer-events: none; pointer-events: none;
box-shadow : var(--mediaItemShadow); box-shadow: var(--mediaItemShadow);
z-index : 1; z-index: 1;
border-radius : inherit; border-radius: inherit;
} }
.modal-header { .modal-header {
width : 100%; width: 100%;
padding: 6px; padding: 6px;
} }
.modal-content { .modal-content {
width : 100%; width: 100%;
height : 100%; height: 100%;
overflow : hidden; overflow: hidden;
overflow-y: overlay; overflow-y: overlay;
} }
.modal-footer {} .modal-footer {
}
} }
} }
.spatialproperties-panel { .spatialproperties-panel {
.modal-window { .modal-window {
&:not(.airplay-modal){ &:not(.airplay-modal) {
height : 700px; height: 700px;
max-height: 700px; max-height: 700px;
width : 800px; width: 800px;
max-width : 800px;} max-width: 800px;
overflow : hidden; }
overflow: hidden;
.info-header { .info-header {
padding-left: 12px; padding-left: 12px;
} }
.visual-container { .visual-container {
display : flex; display: flex;
justify-content: center; justify-content: center;
align-items : center; align-items: center;
overflow : hidden; overflow: hidden;
} }
.visual { .visual {
position : relative; position: relative;
height : 250px; height: 250px;
width : 300px; width: 300px;
display : inline-flex; display: inline-flex;
align-items : flex-end; align-items: flex-end;
justify-content: center; justify-content: center;
filter : drop-shadow(2px 12px 6px rgb(0 0 0 / 25%)); filter: drop-shadow(2px 12px 6px rgb(0 0 0 / 25%));
margin : 0 auto; margin: 0 auto;
.face { .face {
position : absolute; position: absolute;
width : calc(12px * 6); width: calc(12px * 6);
height : calc(12px * 6); height: calc(12px * 6);
border-radius: 6px; border-radius: 6px;
transform : rotateX(60deg) rotateZ(-45deg); transform: rotateX(60deg) rotateZ(-45deg);
transition : transform 0.2s linear, width 0.2s linear, height 0.2s linear; transition: transform 0.2s linear, width 0.2s linear, height 0.2s linear;
} }
.listener { .listener {
position : absolute; position: absolute;
width : 32px; width: 32px;
height : 32px; height: 32px;
border-radius: 6px; border-radius: 6px;
transform : rotateX(60deg) rotateZ(-45deg); transform: rotateX(60deg) rotateZ(-45deg);
transition : transform 0.2s linear, width 0.2s linear, height 0.2s linear; transition: transform 0.2s linear, width 0.2s linear, height 0.2s linear;
background : white; background: white;
color : black; color: black;
z-index : 2; z-index: 2;
} }
.audiosource { .audiosource {
position : absolute; position: absolute;
width : 32px; width: 32px;
height : 32px; height: 32px;
border-radius: 6px; border-radius: 6px;
transform : rotateX(60deg) rotateZ(-45deg); transform: rotateX(60deg) rotateZ(-45deg);
transition : transform 0.2s linear, width 0.2s linear, height 0.2s linear; transition: transform 0.2s linear, width 0.2s linear, height 0.2s linear;
background : yellow; background: yellow;
z-index : 2; z-index: 2;
} }
.face:nth-of-type(1) { .face:nth-of-type(1) {
background: linear-gradient(45deg, #28223a, #1f2038); background: linear-gradient(45deg, #28223a, #1f2038);
z-index : 1; z-index: 1;
} }
.face:nth-of-type(2) { .face:nth-of-type(2) {
background: linear-gradient(45deg, #7d53ad, #5763ff); background: linear-gradient(45deg, #7d53ad, #5763ff);
transform : rotateX(60deg) rotateZ(-45deg) translateZ(30px); transform: rotateX(60deg) rotateZ(-45deg) translateZ(30px);
opacity : 0.7; opacity: 0.7;
z-index : 3; z-index: 3;
} }
} }
.modal-header { .modal-header {
padding : 16px; padding: 16px;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
@ -162,14 +165,14 @@
.addtoplaylist-panel { .addtoplaylist-panel {
.modal-window { .modal-window {
max-height : 600px; max-height: 600px;
max-width : 400px; max-width: 400px;
background : rgb(18 18 18 / 90%); background: rgb(18 18 18 / 90%);
overflow : hidden; overflow: hidden;
backdrop-filter: blur(16px) saturate(180%); backdrop-filter: blur(16px) saturate(180%);
.modal-header { .modal-header {
padding : 16px; padding: 16px;
position: relative; position: relative;
.modal-title { .modal-title {
@ -182,34 +185,34 @@
} }
.modal-search { .modal-search {
width : 100%; width: 100%;
padding : 0px 16px; padding: 0px 16px;
position: relative; position: relative;
} }
.playlist-item { .playlist-item {
appearance : none; appearance: none;
border : 0px; border: 0px;
text-align : left; text-align: left;
width : 100%; width: 100%;
margin : 0; margin: 0;
display : flex; display: flex;
background : rgba(32, 32, 32, 0.46); background: rgba(32, 32, 32, 0.46);
color : #eee; color: #eee;
font-family: inherit; font-family: inherit;
font-size : 0.98em; font-size: 0.98em;
padding : 6px 12px; padding: 6px 12px;
align-items: center; align-items: center;
flex-flow : row; flex-flow: row;
.icon { .icon {
pointer-events : none; pointer-events: none;
width : 32px; width: 32px;
height : 32px; height: 32px;
display : flex; display: flex;
justify-content: center; justify-content: center;
align-items : center; align-items: center;
margin-right : 6px; margin-right: 6px;
} }
.name { .name {
@ -236,35 +239,35 @@
} }
.menu-panel { .menu-panel {
width : 100%; width: 100%;
height : 100%; height: 100%;
position : fixed; position: fixed;
top : 0; top: 0;
left : 0; left: 0;
z-index : 100001; z-index: 100001;
display : flex; display: flex;
justify-content : center; justify-content: center;
align-items : center; align-items: center;
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
.menu-header-body { .menu-header-body {
padding : 6px; padding: 6px;
display : flex; display: flex;
background: rgb(200 200 200 / 10%); background: rgb(200 200 200 / 10%);
.menu-option-header { .menu-option-header {
width : 40px; width: 40px;
height : 40px; height: 40px;
display : flex; display: flex;
justify-content: center; justify-content: center;
align-items : center; align-items: center;
border-radius : var(--mediaItemRadius); border-radius: var(--mediaItemRadius);
appearance : none; appearance: none;
border : 0; border: 0;
background : transparent; background: transparent;
&.active { &.active {
.sidebar-icon>.svg-icon { .sidebar-icon > .svg-icon {
--color: var(--keyColor); --color: var(--keyColor);
} }
} }
@ -280,62 +283,62 @@
} }
.menu-panel-body { .menu-panel-body {
display : flex; display: flex;
flex-flow : column; flex-flow: column;
background : rgb(30 30 30 / 45%); background: rgb(30 30 30 / 45%);
backdrop-filter: blur(32px) saturate(180%); backdrop-filter: blur(32px) saturate(180%);
position : relative; position: relative;
min-width : 200px; min-width: 200px;
box-shadow : var(--ciderShadow-Generic); box-shadow: var(--ciderShadow-Generic);
border-radius : var(--panelRadius); border-radius: var(--panelRadius);
overflow : hidden; overflow: hidden;
font-size : 13px; font-size: 13px;
.menu-option { .menu-option {
text-align: left; text-align: left;
display : flex; display: flex;
appearance: none; appearance: none;
border : 0px; border: 0px;
font : inherit; font: inherit;
background: transparent; background: transparent;
color : inherit; color: inherit;
margin : 0 auto; margin: 0 auto;
position : relative; position: relative;
width : 100%; width: 100%;
padding : 9px 14px; padding: 9px 14px;
align-items: center; align-items: center;
&::before { &::before {
background : var(--hover); background: var(--hover);
border-radius: 6px; border-radius: 6px;
content : ""; content: "";
--sizeY : 3px; --sizeY: 3px;
--sizeX : 4px; --sizeX: 4px;
top : var(--sizeY); top: var(--sizeY);
left : var(--sizeX); left: var(--sizeX);
bottom : var(--sizeY); bottom: var(--sizeY);
right : var(--sizeX); right: var(--sizeX);
position : absolute; position: absolute;
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 .25s ease-out, opacity .25s ease-out;
} }
&:hover { &:hover {
&::before { &::before {
transition: transform 0s ease-in, opacity 0s ease-in; transition: transform 0s ease-in, opacity 0s ease-in;
opacity : 1; opacity: 1;
transform : scale(1); transform: scale(1);
} }
} }
&:active { &:active {
&::before { &::before {
transition: transform .1s ease-in-out, opacity .1s ease-in-out; transition: transform .1s ease-in-out, opacity .1s ease-in-out;
opacity : 1; opacity: 1;
transform : scale(0.98); transform: scale(0.98);
background: var(--selected-click); background: var(--selected-click);
} }
} }
@ -375,25 +378,25 @@
} }
.menu-body { .menu-body {
overflow : overlay; overflow: overlay;
height : 100%; height: 100%;
display : flex; display: flex;
flex-flow: column; flex-flow: column;
gap : 0px; gap: 0px;
padding : 0px; padding: 0px;
position : relative; position: relative;
} }
.menu-footer { .menu-footer {
width : 100%; width: 100%;
padding: 12px; padding: 12px;
} }
} }
.queue-panel { .queue-panel {
height : 100%; height: 100%;
width : 100%; width: 100%;
display : flex; display: flex;
flex-flow: column; flex-flow: column;
.queue-header-text { .queue-header-text {
@ -402,52 +405,52 @@
.queue-body { .queue-body {
overflow: overlay; overflow: overlay;
height : 100%; height: 100%;
} }
.queue-footer { .queue-footer {
width : 100%; width: 100%;
padding: 12px; padding: 12px;
} }
.autoplay { .autoplay {
background : rgb(200 200 200 / 15%); background: rgb(200 200 200 / 15%);
display : flex; display: flex;
justify-content: center; justify-content: center;
appearance : none; appearance: none;
border : 0; border: 0;
border-radius : 6px; border-radius: 6px;
height : 32px; height: 32px;
width : 32px; width: 32px;
} }
.infinity { .infinity {
content: url("./assets/infinity.svg"); content: url("./assets/infinity.svg");
margin : auto; margin: auto;
} }
} }
.moreinfo-modal { .moreinfo-modal {
.modal-window { .modal-window {
height : 70%; height: 70%;
max-height : 100%; max-height: 100%;
width : 45%; width: 45%;
max-width : 100%; max-width: 100%;
overflow : hidden; overflow: hidden;
line-height: 1.25; line-height: 1.25;
} }
.modal-content { .modal-content {
padding : 1em; padding: 1em;
font-size: 0.8rem; font-size: 0.8rem;
br { br {
display : block; display: block;
/* makes it have a width */ /* makes it have a width */
content : ""; content: "";
/* clears default height */ /* clears default height */
margin : 2em; margin: 2em;
margin-bottom: -0.6rem; margin-bottom: -0.6rem;
} }
} }
@ -457,7 +460,7 @@
.modal-title { .modal-title {
text-align: unset !important; text-align: unset !important;
width : 100%; width: 100%;
&:not(.modal-subtitle) { &:not(.modal-subtitle) {
font-size: 25px; font-size: 25px;
@ -479,8 +482,9 @@
top: 0; top: 0;
left: 0; left: 0;
z-index: -1; z-index: -1;
filter:blur(32px) brightness(50%) saturate(280%); filter: blur(32px) brightness(50%) saturate(280%);
} }
.popover-artwork { .popover-artwork {
width: 200px; width: 200px;
height: 200px; height: 200px;
@ -491,7 +495,8 @@
.song-name { .song-name {
font-weight: 600; font-weight: 600;
} }
.song-artist,.song-album {
.song-artist, .song-album {
opacity: 0.75; opacity: 0.75;
cursor: pointer; cursor: pointer;
@ -501,3 +506,19 @@
} }
} }
} }
._svg-icon {
--icon: url("./assets/chevron-left.svg");
--size: 1em;
width: var(--size);
height: var(--size);
-webkit-mask-image: var(--icon);
-webkit-mask-position: center;
-webkit-mask-size: contain;
background: rgb(255 255 255 / 76%);
-webkit-mask-repeat: no-repeat;
&.md {
--size: 1.2em;
}
}

View file

@ -1,15 +1,17 @@
body[platform="darwin"] { body[platform="darwin"] {
html { html {
background: transparent!important; background: transparent !important;
} }
&.notransparency::before { &.notransparency::before {
display: none; display: none;
} }
#app { #app {
&.simplebg { &.simplebg {
background: transparent; background: transparent;
} }
&::before { &::before {
display: none; display: none;
} }
@ -25,6 +27,7 @@ body[platform="darwin"] {
.app-chrome .app-chrome-item.search { .app-chrome .app-chrome-item.search {
margin-right: 12px; margin-right: 12px;
} }
.app-chrome .app-mainmenu { .app-chrome .app-mainmenu {
width: 46px; width: 46px;
} }
@ -33,6 +36,21 @@ body[platform="darwin"] {
background-color: var(--macOSChromeColor); 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 { #app-main {
@ -43,7 +61,19 @@ body[platform="darwin"] {
} }
#app-content { #app-content {
background-color: var(--baseColor); background-color: #1e1e1e6b;
} }
} }
.settings-window.maxed {
.tabs>.col-auto {
transition: padding-top .3s linear;
padding-top: var(--chromeHeight1);
}
}
#apple-music-video-player-controls #player-exit {
margin-top: 18px;
left: 70px;
}
} }

View file

@ -0,0 +1,410 @@
.mini-view {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.fs-row {
flex-grow: 1;
}
.playback-button--small.active {
background-color: rgb(200 200 200 / 25%);
}
.player-exit {
z-index: 3;
position: absolute;
top: 5px;
right: 5px;
-webkit-app-region: no-drag;
}
.player-pin {
z-index: 3;
position: absolute;
min-width: 20px;
min-height: 20px;
top: 5px;
right: 30px;
-webkit-app-region: no-drag;
}
#mini-pin {
display: none;
}
.player-pin:hover #mini-pin {
display: block;
}
.playback-button--small {
opacity: 0.7;
}
.right-col {
height: 50vh;
}
@media only screen and (max-width: 1121px) {
.display--large {
display: flex !important;
}
}
.display--large {
display: flex;
.slider {
width: 100%;
z-index: 1;
}
.input-container {
display: flex;
justify-content: center;
align-items: center;
width: 100%
}
.volume-button--small {
border-radius: 6px;
color: inherit;
background-size: 16px;
background-repeat: no-repeat;
background-position: center;
background-color: transparent;
height: 15px;
width: 30px;
border: 0px;
box-shadow: unset;
opacity: 0.70;
background-image: url("./assets/feather/volume-2.svg");
}
.volume-button--small:active {
transform: scale(0.9);
}
.volume-button--small.active {
background-image: url("./assets/feather/volume.svg");
}
input[type=range] {
-webkit-appearance: none;
height: 4px;
background: rgba(255, 255, 255, 0.4);
border-radius: 5px;
background-size: 70% 100%;
background-repeat: no-repeat;
&::-webkit-slider-thumb {
-webkit-appearance: none;
height: 14px;
width: 14px;
border-radius: 50%;
background: rgb(50 50 50);
cursor: default;
box-shadow: inset 0px 0px 0px 1px rgba(255, 255, 255, 0.4);
transition: all var(--appleTransition);
}
&::-webkit-slider-thumb:hover {
background-image: radial-gradient(var(--songProgressColor) 2px, transparent 3px, transparent 10px);
transform: scale(1.2);
}
&::-webkit-slider-thumb:active {
background-image: radial-gradient(var(--songProgressColor) 3px, transparent 4px, transparent 10px);
transform: scale(1);
}
&::-webkit-slider-runnable-track {
-webkit-appearance: none;
box-shadow: none;
border: none;
background: transparent;
}
}
}
.background {
position: absolute;
background-size: cover;
width: 100%;
height: 100%;
-webkit-user-select: none;
-webkit-app-region: drag;
.bgArtworkMaterial {
position: absolute;
width: 100%;
height: 100%;
.bg-artwork-container {
z-index: unset;
}
.bg-artwork-container .bg-artwork {
filter: brightness(85%) saturate(95%) blur(180px) contrast(0.9) opacity(0.9);
}
.no-animation {
animation: unset;
}
}
}
.lyrics-col {
height: 62vh;
display: flex;
justify-content: center;
align-content: center;
width: 80%;
::-webkit-scrollbar-thumb {
box-shadow: unset;
}
&:hover ::-webkit-scrollbar-thumb {
box-shadow: inset 0px 0px 10px 10px rgb(200 200 200 / 50%);
}
.no-lyrics {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
}
.lyric-line {
font-size: 35px;
}
}
.queue-col {
width: 60vh;
height: 50vh;
.queue-title {
opacity: 0.6;
}
.queue-panel > * {
z-index: 3;
}
::-webkit-scrollbar-thumb {
box-shadow: unset;
}
&:hover ::-webkit-scrollbar-thumb {
box-shadow: inset 0px 0px 10px 10px rgb(200 200 200 / 50%);
}
}
.tab-toggles {
display: flex;
position: absolute;
bottom: 0;
right: 0;
width: 15vh;
height: 5vh;
justify-content: space-evenly;
.volume {
background-image: url("./assets/feathers/volume.svg");
padding: 0.5vh;
width: 2vh;
height: 2vh;
background-origin: content-box;
background-repeat: no-repeat;
}
.queue {
background-image: url("./assets/list.svg");
padding: 0.5vh;
width: 2.5vh;
height: 2.5vh;
background-origin: content-box;
background-repeat: no-repeat;
}
.lyrics {
background-image: url("./assets/quote-right.svg");
padding: 0.5vh;
width: 2.5vh;
height: 2.5vh;
background-origin: content-box;
background-repeat: no-repeat;
}
.active {
background-color: rgba(200, 200, 200, 0.7);
border-radius: 3px;
}
}
.artwork-col {
justify-content: center;
align-items: center;
display: flex;
flex-direction: column;
.artwork {
width: 100%;
height: 100%;
.mediaitem-artwork {
border-radius: unset;
}
}
.controls-parents {
width: 100%;
position: absolute;
background: #0000009e;
backdrop-filter: blur(10px);
bottom: 0px;
z-index: 3;
opacity: 0;
padding: 3%;
&:hover {
opacity: 1;
}
}
.app-playback-controls {
-webkit-app-region: no-drag;
.song-artist, .song-name {
font-weight: 600;
text-align: center;
font-size: 0.9em;
height: 1.2em;
line-height: 0.9em;
overflow: hidden;
text-overflow: ellipsis;
max-width: 360px;
.song-name-normal {
height: inherit;
}
&.song-artist-marquee {
> marquee {
//margin-bottom: -3px;
}
}
}
.song-artist {
font-size: 0.875em;
font-weight: 400;
}
.song-name {
width: unset !important;
margin-top: 0.15vh;
display: -webkit-box;
line-height: 1.2;
text-overflow: ellipsis;
text-align: center;
}
}
.app-playback-controls .playback-info {
margin-top: 0.5vh;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
position: relative;
input[type="range"] {
width: 100%;
}
> div {
width: 100%;
text-align: center;
}
}
.app-playback-controls .song-progress {
@bgColor: transparent;
//height: 16px;
position: absolute;
bottom: -3.5vh;
left: 0px;
background: @bgColor;
.song-duration p {
font-weight: 400;
font-size: 10px;
height: 1.2em;
line-height: 1.3em;
overflow: hidden;
margin: 0 0 0 0.25em;
}
&:hover {
> input[type=range] {
&::-webkit-slider-thumb {
opacity: 1;
transform: scale(1);
z-index: 1;
}
}
}
input[type=range] {
appearance: none;
width: 100%;
height: 4px;
background-color: rgb(200 200 200 / 10%);
border-radius: 2px;
&::-webkit-slider-thumb {
opacity: 0;
transform: scale(0.5);
-webkit-appearance: none;
appearance: none;
width: 12px;
height: 12px;
border-radius: 100%;
background: var(--songProgressColor);
cursor: default;
transition: opacity .10s var(--appleEase), transform .10s var(--appleEase);
}
&::-moz-range-thumb {
width: 8px;
height: 8px;
border-radius: 100%;
background: var(--songProgressColor);
cursor: default;
}
}
}
.control-buttons {
margin-top: 3.5vh;
display: inline-flex;
width: 100%;
justify-content: center;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,8 @@ import {Events} from './events.js'
import { wsapi } from "./wsapi_interop.js" import { wsapi } from "./wsapi_interop.js"
import { MusicKitTools } from "./musickittools.js" import { MusicKitTools } from "./musickittools.js"
import { spawnMica } from "./mica.js" import { spawnMica } from "./mica.js"
import { svgIcon } from './components/svg-icon.js'
import { sidebarLibraryItem } from './components/sidebar-library-item.js'
// Define window objects // Define window objects
@ -17,11 +19,22 @@ window.CiderCache = CiderCache
window.CiderFrontAPI = CiderFrontAPI window.CiderFrontAPI = CiderFrontAPI
window.wsapi = wsapi window.wsapi = wsapi
if (app.cfg.advanced.disableLogging === true) {
window.console = {
log: function() {},
error: function() {},
warn: function() {},
assert: function() {},
debug: function() {}
}
}
// Mount Vue to #app // Mount Vue to #app
app.$mount("#app") app.$mount("#app")
// Init CiderAudio // Init CiderAudio
if (app.cfg.advanced.AudioContext){ if (app.cfg.advanced.AudioContext === true) {
CiderAudio.init() CiderAudio.init()
} }

View file

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

View file

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

View file

@ -24,7 +24,7 @@ const Events = {
// CTRL + R // CTRL + R
if (event.keyCode === 82 && event.ctrlKey) { if (event.keyCode === 82 && event.ctrlKey) {
event.preventDefault() event.preventDefault()
bootbox.confirm(app.getLz('term.reload'), (res)=>{ app.confirm(app.getLz('term.reload'), (res)=>{
if (res) { if (res) {
window.location.reload() window.location.reload()
} }

View file

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

View file

@ -21,22 +21,22 @@ async function spawnMica() {
let lastScreenWidth; let lastScreenWidth;
let lastScreenHeight; let lastScreenHeight;
let regen = true; let imgSrc = "";
let imgSrc = await ipcRenderer.sendSync("get-wallpaper", { let micaCache = await CiderCache.getCache("mica-cache");
if (!micaCache) {
micaCache = {
path: "",
data: "",
};
}
if (micaCache.path == imgSrc.path) {
imgSrc = micaCache;
}else{
imgSrc = await ipcRenderer.sendSync("get-wallpaper", {
blurAmount: 256 blurAmount: 256
}); });
CiderCache.putCache("mica-cache", imgSrc);
// let micaCache = await CiderCache.getCache("mica-cache"); }
// if (!micaCache) {
// micaCache = {
// path: "",
// data: "",
// };
// }
// if (micaCache.path == imgSrc.path) {
// regen = false;
// imgSrc = micaCache;
// }
let canvas = document.createElement("canvas"); let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d"); let ctx = canvas.getContext("2d");
let img = new Image(); let img = new Image();

View file

@ -12,6 +12,7 @@ const app = new Vue({
ipcRenderer: ipcRenderer, ipcRenderer: ipcRenderer,
cfg: ipcRenderer.sendSync("getStore"), cfg: ipcRenderer.sendSync("getStore"),
isDev: ipcRenderer.sendSync("is-dev"), isDev: ipcRenderer.sendSync("is-dev"),
clientPort: ipcRenderer.sendSync("get-port"),
drawertest: false, drawertest: false,
platform: "", platform: "",
mk: {}, mk: {},
@ -147,6 +148,7 @@ const app = new Vue({
start: 0, start: 0,
end: 0 end: 0
}, },
lyricOffset: 0,
v3: { v3: {
requestBody: { requestBody: {
platform: "web" platform: "web"
@ -168,6 +170,7 @@ const app = new Vue({
location: "", location: "",
info: {} info: {}
}, },
windowState: "normal",
desiredPageTransition: "wpfade_transform", desiredPageTransition: "wpfade_transform",
hideUserInfo: ipcRenderer.sendSync("is-dev") || false, hideUserInfo: ipcRenderer.sendSync("is-dev") || false,
artworkReady: false, artworkReady: false,
@ -215,8 +218,10 @@ const app = new Vue({
audioPlaybackRate: false, audioPlaybackRate: false,
showPlaylist: false, showPlaylist: false,
castMenu: false, castMenu: false,
pathMenu: false,
moreInfo: false, moreInfo: false,
airplayPW: false, airplayPW: false,
settings: false
}, },
socialBadges: { socialBadges: {
badgeMap: {}, badgeMap: {},
@ -243,6 +248,7 @@ const app = new Vue({
notyf: notyf, notyf: notyf,
idleTimer: null, idleTimer: null,
idleState: false, idleState: false,
appVisible: true
}, },
watch: { watch: {
cfg: { cfg: {
@ -274,6 +280,12 @@ const app = new Vue({
}, false) }, false)
}, },
methods: { methods: {
hotReload() {
this.appVisible = false
setTimeout(() => {
this.appVisible = true
}, 1000)
},
setWindowHash(route = "") { setWindowHash(route = "") {
window.location.hash = `#${route}`; window.location.hash = `#${route}`;
}, },
@ -325,11 +337,7 @@ const app = new Vue({
let advancedTooltip = this.cfg.audio.dBSPL ? (Number(this.cfg.audio.dBSPLcalibration) + (Math.log10(this.mk.volume) * 20)).toFixed(2) + ' dB SPL' : (Math.log10(this.mk.volume) * 20).toFixed(2) + ' dBFS' let advancedTooltip = this.cfg.audio.dBSPL ? (Number(this.cfg.audio.dBSPLcalibration) + (Math.log10(this.mk.volume) * 20)).toFixed(2) + ' dB SPL' : (Math.log10(this.mk.volume) * 20).toFixed(2) + ' dBFS'
return this.cfg.audio.advanced ? advancedTooltip : (this.mk.volume * 100).toFixed(0) + '%' return this.cfg.audio.advanced ? advancedTooltip : (this.mk.volume * 100).toFixed(0) + '%'
}, },
mainMenuVisibility(val, isContextMenu) { mainMenuVisibility(val) {
if (this.chrome.sidebarCollapsed && !isContextMenu) {
this.chrome.sidebarCollapsed = false
return
}
if (val) { if (val) {
(this.mk.isAuthorized) ? this.chrome.menuOpened = !this.chrome.menuOpened : false; (this.mk.isAuthorized) ? this.chrome.menuOpened = !this.chrome.menuOpened : false;
if (!this.mk.isAuthorized) { if (!this.mk.isAuthorized) {
@ -392,6 +400,46 @@ const app = new Vue({
return message return message
} }
}, },
getProfileLz(type, name) { // For Spatial and CAR.
let result = "";
// Hard-coded shiz
switch (name) {
case "Maikiwi":
return "Maikiwi";
break;
case "Maikiwi+":
return "Maikiwi+";
break;
case "Minimal+":
return this.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.minimal') + "+";
break;
case "live":
return "LIVE";
break;
}
switch (type) {
case "CAR":
result = this.getLz('settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.' + name);
if (result === "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode." + name) {
return name;
}
else {return result;}
break;
case "CTS":
result = this.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.' + name.toLowerCase());
if (result === "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile." + name.toLowerCase()) {
return name;
}
else {return result;}
break;
default:
return name;
}
},
setLzManual() { setLzManual() {
app.$data.library.songs.sortingOptions = { app.$data.library.songs.sortingOptions = {
"albumName": app.getLz('term.sortBy.album'), "albumName": app.getLz('term.sortBy.album'),
@ -440,6 +488,9 @@ const app = new Vue({
} }
}) })
}, },
quit() {
ipcRenderer.invoke("quit-app")
},
async openAppleMusicURL(url) { async openAppleMusicURL(url) {
let properties = MusicKit.formattedMediaURL(url) let properties = MusicKit.formattedMediaURL(url)
let item = { let item = {
@ -695,6 +746,9 @@ const app = new Vue({
} catch (err) { } catch (err) {
} }
// Used to get a scale factor for the window for CSS scaling
window.addEventListener("resize", e => this.setWindowScaleFactor())
this.setWindowScaleFactor()
this.mk._bag.features['seamless-audio-transitions'] = this.cfg.audio.seamless_audio this.mk._bag.features['seamless-audio-transitions'] = this.cfg.audio.seamless_audio
this.mk._bag.features["broadcast-radio"] = true this.mk._bag.features["broadcast-radio"] = true
this.mk._services.apiManager.store.storekit._restrictedEnabled = false this.mk._services.apiManager.store.storekit._restrictedEnabled = false
@ -824,6 +878,10 @@ const app = new Vue({
MusicKit.getInstance().videoContainerElement = document.getElementById("apple-music-video-player") MusicKit.getInstance().videoContainerElement = document.getElementById("apple-music-video-player")
ipcRenderer.on('setStoreValue', (e, key, value) => {
app.cfg[key] = value
})
ipcRenderer.on('theme-update', async (event, arg) => { ipcRenderer.on('theme-update', async (event, arg) => {
await less.refresh(true, true, true) await less.refresh(true, true, true)
self.setTheme(self.cfg.visual.theme, true) self.setTheme(self.cfg.visual.theme, true)
@ -846,10 +904,14 @@ const app = new Vue({
}) })
ipcRenderer.on('getUpdatedLocalList', (event, data) => { ipcRenderer.on('getUpdatedLocalList', (event, data) => {
console.log("cider-local", data); // console.log("cider-local", data);
this.library.localsongs = data; this.library.localsongs = data;
}) })
ipcRenderer.on('window-state-changed', (event, data) => {
this.chrome.windowState = data
})
ipcRenderer.on('SoundCheckTag', (event, tag) => { ipcRenderer.on('SoundCheckTag', (event, tag) => {
// let replaygain = self.parseSCTagToRG(tag) // let replaygain = self.parseSCTagToRG(tag)
try { try {
@ -869,6 +931,7 @@ const app = new Vue({
try { try {
//CiderAudio.audioNodes.gainNode.gain.value = (Math.min(Math.pow(10, (replaygain.gain / 20)), (1 / replaygain.peak))) //CiderAudio.audioNodes.gainNode.gain.value = (Math.min(Math.pow(10, (replaygain.gain / 20)), (1 / replaygain.peak)))
CiderAudio.audioNodes.gainNode.gain.value = gain CiderAudio.audioNodes.gainNode.gain.value = gain
CiderAudio.hierarchical_loading();
} catch (e) { } catch (e) {
} }
} }
@ -896,13 +959,19 @@ const app = new Vue({
} }
}); });
this.mk.addEventListener(MusicKit.Events.playbackProgressDidChange, () => {
if (self.mk.currentPlaybackProgress === (app.cfg.connectivity.lastfm.scrobble_after / 100)) {
ipcRenderer.send('lastfm:scrobbleTrack', MusicKitInterop.getAttributes());
}
})
this.mk.addEventListener(MusicKit.Events.playbackStateDidChange, (event) => { this.mk.addEventListener(MusicKit.Events.playbackStateDidChange, (event) => {
ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes()); ipcRenderer.send('wsapi-updatePlaybackState', wsapi.getAttributes());
document.body.setAttribute("playback-state", event.state == 2 ? "playing" : "paused") document.body.setAttribute("playback-state", event.state == 2 ? "playing" : "paused")
}) })
this.mk.addEventListener(MusicKit.Events.playbackTimeDidChange, (a) => { this.mk.addEventListener(MusicKit.Events.playbackTimeDidChange, (a) => {
self.lyriccurrenttime = self.mk.currentPlaybackTime // self.lyriccurrenttime = self.mk.currentPlaybackTime - app.lyricOffset
this.currentSongInfo = a this.currentSongInfo = a
self.playerLCD.playbackDuration = (self.mk.currentPlaybackTime) self.playerLCD.playbackDuration = (self.mk.currentPlaybackTime)
// wsapi // wsapi
@ -922,10 +991,10 @@ const app = new Vue({
app.mk.nowPlayingItem.attributes.name = e.title app.mk.nowPlayingItem.attributes.name = e.title
app.mk.nowPlayingItem.attributes.artistName = e.performer app.mk.nowPlayingItem.attributes.artistName = e.performer
app.mk.nowPlayingItem.attributes.albumName = e.album app.mk.nowPlayingItem.attributes.albumName = e.album
if(e.links[1]) { if (e.links[1]) {
app.currentArtUrl = e.links[1].url app.currentArtUrl = e.links[1].url
app.currentArtUrlRaw = e.links[1].url app.currentArtUrlRaw = e.links[1].url
}else{ } else {
app.currentArtUrl = e.links[0].url app.currentArtUrl = e.links[0].url
app.currentArtUrlRaw = e.links[0].url app.currentArtUrlRaw = e.links[0].url
} }
@ -937,50 +1006,63 @@ const app = new Vue({
if (self.$refs.queue) { if (self.$refs.queue) {
self.$refs.queue.updateQueue(); self.$refs.queue.updateQueue();
} }
this.currentSongInfo = a this.currentSongInfo = a;
if (this.currentSongInfo === null || this.currentSongInfo === undefined) { return; } // EVIL EMPTY OBJECTS BE GONE
if (app.cfg.advanced.AudioContext) { let localFiles = false;
try { try {
if (app.mk.nowPlayingItem.flavor.includes("64")) { if (app.mk.nowPlayingItem.flavor.includes("64") && app.mk.nowPlayingItem.flavor.includes(":")) {
if (localStorage.getItem("playingBitrate") !== "64") {
localStorage.setItem("playingBitrate", "64") localStorage.setItem("playingBitrate", "64")
CiderAudio.hierarchical_loading(); } else if (app.mk.nowPlayingItem.flavor.includes("256") && app.mk.nowPlayingItem.flavor.includes(":")) {
}
} else if (app.mk.nowPlayingItem.flavor.includes("256")) {
if (localStorage.getItem("playingBitrate") !== "256") {
localStorage.setItem("playingBitrate", "256") localStorage.setItem("playingBitrate", "256")
CiderAudio.hierarchical_loading();
}
} else { } else {
localStorage.setItem("playingBitrate", "256") localFiles = true;
CiderAudio.hierarchical_loading(); localStorage.setItem("playingBitrate", app.mk.nowPlayingItem.flavor)
} }
} catch (e) { } catch (e) {
localStorage.setItem("playingBitrate", "256") localFiles = true;
CiderAudio.hierarchical_loading(); try {localStorage.setItem("playingBitrate", app.mk.nowPlayingItem.flavor)}
} catch(e) {}
} }
if (!app.cfg.audio.normalization || app.cfg.advanced.AudioContext === true) { CiderAudio.hierarchical_loading(); }
if (app.cfg.audio.normalization) { else {
// get unencrypted audio previews to get SoundCheck's normalization tag // get unencrypted audio previews to get SoundCheck's normalization tag
try { try {
let previewURL = null let previewURL = null
try { try {
previewURL = app.mk.nowPlayingItem.previewURL previewURL = app.mk.nowPlayingItem.previewURL
} catch (e) { } catch (e) {
if (e instanceof TypeError === false) { console.debug("[Cider][MaikiwiSoundCheck] normalizer function err: " + e) }
else {
if (localFiles === true) {CiderAudio.audioNodes.gainNode.gain.value = 0.8222426499470}
}
} }
if (previewURL == null && ((app.mk.nowPlayingItem?._songId ?? (app.mk.nowPlayingItem["songId"] ?? app.mk.nowPlayingItem.relationships.catalog.data[0].id)) != -1)) { if (previewURL == null && ((app.mk.nowPlayingItem?._songId ?? (app.mk.nowPlayingItem["songId"] ?? app.mk.nowPlayingItem.relationships.catalog.data[0].id)) != -1)) {
app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/songs/${app.mk.nowPlayingItem?._songId ?? (app.mk.nowPlayingItem["songId"] ?? app.mk.nowPlayingItem.relationships.catalog.data[0].id)}`).then((response) => { app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/songs/${app.mk.nowPlayingItem?._songId ?? (app.mk.nowPlayingItem["songId"] ?? app.mk.nowPlayingItem.relationships.catalog.data[0].id)}`).then((response) => {
previewURL = response.data.data[0].attributes.previews[0].url try {previewURL = response.data.data[0].attributes.previews[0].url;} catch(e) {
if (previewURL) if (e instanceof TypeError === false) { console.debug("[Cider][MaikiwiSoundCheck] normalizer function err: " + e) }
else {
if (localFiles === true) {CiderAudio.audioNodes.gainNode.gain.value = 0.8222426499470}}
}
if (previewURL) {
console.debug("[Cider][MaikiwiSoundCheck] previewURL response.data.data[0].attributes.previews[0].url: " + previewURL)
ipcRenderer.send('getPreviewURL', previewURL) ipcRenderer.send('getPreviewURL', previewURL)
}
else {
if (localFiles === true) {CiderAudio.audioNodes.gainNode.gain.value = 0.8222426499470}
}
}) })
} else { } else {
if (previewURL) if (previewURL) {
ipcRenderer.send('getPreviewURL', previewURL) console.debug("[Cider][MaikiwiSoundCheck] previewURL in app.mk.nowPlayingItem.previewURL: " + previewURL)
ipcRenderer.send('getPreviewURL', previewURL)}
} }
} catch (e) { } catch (e) {
if (e instanceof TypeError === false) { console.debug("[Cider][MaikiwiSoundCheck] normalizer function err: " + e) }
else {
if (localFiles === true) {CiderAudio.audioNodes.gainNode.gain.value = 0.8222426499470}
}
} }
} }
@ -1056,6 +1138,20 @@ const app = new Vue({
if (this.cfg.general.themeUpdateNotification && !this.isDev) { if (this.cfg.general.themeUpdateNotification && !this.isDev) {
this.checkForThemeUpdates() this.checkForThemeUpdates()
} }
ipcRenderer.invoke("scanLibrary")
},
setWindowScaleFactor() {
let scale = window.devicePixelRatio * window.innerWidth / 1280 * window.innerHeight / 720
let desiredScale = clamp(parseFloat(app.cfg.visual.maxElementScale == -1 ? 1.5 : app.cfg.visual.maxElementScale), 1, 1.5)
app.$store.state.windowRelativeScale = scale
if(scale <= 1) {
scale = 1
}else if(scale >= desiredScale) {
scale = desiredScale
}
document.documentElement.style
.setProperty('--windowRelativeScale', scale);
}, },
showFoo(querySelector, time) { showFoo(querySelector, time) {
clearTimeout(this.idleTimer); clearTimeout(this.idleTimer);
@ -1086,7 +1182,7 @@ const app = new Vue({
message: `[Themes] ${theme.name} has an update available.` message: `[Themes] ${theme.name} has an update available.`
}) })
notify.on("click", () => { notify.on("click", () => {
app.appRoute("themes-github") app.openSettingsPage("github-themes")
notyf.dismiss(notify) notyf.dismiss(notify)
}) })
} }
@ -1170,11 +1266,11 @@ const app = new Vue({
} else if (this.cfg.visual.directives[directive]) { } else if (this.cfg.visual.directives[directive]) {
return this.cfg.visual.directives[directive] return this.cfg.visual.directives[directive]
} else { } else {
return "" return false
} }
}, },
unauthorize() { unauthorize() {
bootbox.confirm(app.getLz('term.confirmLogout'), function (result) { this.confirm(app.getLz('term.confirmLogout'), function (result) {
if (result) { if (result) {
app.mk.unauthorize() app.mk.unauthorize()
document.location.reload() document.location.reload()
@ -1295,7 +1391,7 @@ const app = new Vue({
results.forEach(result => { results.forEach(result => {
try { try {
if (result.relationships?.catalog?.data[0]?.attributes?.inFavorites) { if (result.relationships?.catalog?.data[0]?.attributes?.inFavorites) {
if(!favs.includes(result.relationships?.catalog?.data[0].id)) { if (!favs.includes(result.relationships?.catalog?.data[0].id)) {
favs.push(result.relationships?.catalog?.data[0].id) favs.push(result.relationships?.catalog?.data[0].id)
} }
} }
@ -1308,8 +1404,8 @@ const app = new Vue({
return favs return favs
}, },
async setArtistFavorite(id, val = true) { async setArtistFavorite(id, val = true) {
if(val) { if (val) {
if(!app.cfg.home.followedArtists.includes(id)) { if (!app.cfg.home.followedArtists.includes(id)) {
app.cfg.home.followedArtists.push(id) app.cfg.home.followedArtists.push(id)
} }
await app.mk.api.v3.music(`/v1/me/favorites`, { await app.mk.api.v3.music(`/v1/me/favorites`, {
@ -1322,8 +1418,8 @@ const app = new Vue({
method: "POST" method: "POST"
} }
}) })
}else{ } else {
if(app.cfg.home.followedArtists.includes(id)) { if (app.cfg.home.followedArtists.includes(id)) {
app.cfg.home.followedArtists.splice(app.cfg.home.followedArtists.indexOf(id), 1) app.cfg.home.followedArtists.splice(app.cfg.home.followedArtists.indexOf(id), 1)
} }
await app.mk.api.v3.music(`/v1/me/favorites`, { await app.mk.api.v3.music(`/v1/me/favorites`, {
@ -1452,6 +1548,12 @@ const app = new Vue({
action: () => { action: () => {
this.newPlaylistFolder() this.newPlaylistFolder()
} }
},
{
name: app.getLz("action.refresh"),
action: () => {
this.refreshPlaylists()
}
} }
] ]
} }
@ -1549,7 +1651,8 @@ const app = new Vue({
}, },
deletePlaylist(id) { deletePlaylist(id) {
let self = this let self = this
if (confirm(app.getLz('term.deletePlaylist'))) { this.confirm(app.getLz('term.deletePlaylist'), (ok) => {
if (ok) {
app.mk.api.v3.music(`/v1/me/library/playlists/${id}`, {}, { app.mk.api.v3.music(`/v1/me/library/playlists/${id}`, {}, {
fetchOptions: { fetchOptions: {
method: "DELETE" method: "DELETE"
@ -1565,6 +1668,7 @@ const app = new Vue({
}, 8000); }, 8000);
}) })
} }
});
}, },
/** /**
* @param {string} url, href for the initial request * @param {string} url, href for the initial request
@ -1888,7 +1992,7 @@ const app = new Vue({
this.routeView(item.relationships.contents.data[0]) this.routeView(item.relationships.contents.data[0])
} else if (item.attributes?.link?.url != null) { } else if (item.attributes?.link?.url != null) {
if (item.attributes.link.url.includes("viewMultiRoom")) { if (item.attributes.link.url.includes("viewMultiRoom")) {
const params = new Proxy(new URLSearchParams(item.attributes.link.url), { const params = new Proxy(new URLSearchParams(new URL(item.attributes.link.url).search), {
get: (searchParams, prop) => searchParams.get(prop), get: (searchParams, prop) => searchParams.get(prop),
}); });
id = params.fcId id = params.fcId
@ -1902,12 +2006,30 @@ const app = new Vue({
}) })
return; return;
} else if (item.attributes.link.url.includes("viewFeature")) {
const params = new Proxy(new URLSearchParams(new URL(item.attributes.link.url).search), {
get: (searchParams, prop) => searchParams.get(prop),
});
id = params.id
app.mk.api.v3.music(`/v1/editorial/vn/multiplex/${id}?art%5Burl%5D=f&format%5Bresources%5D=map&platform=web`).then(
(data) => {
let item = data.data.results?.target ?? []
app.routeView(item)
}
)
} else { } else {
window.open(item.attributes.link.url) window.open(item.attributes.link.url)
} }
} }
} else if (kind == "multirooms") { } else if (kind == "multiplex") {
app.mk.api.v3.music(`/v1/editorial/vn/multiplex/${id}?art%5Burl%5D=f&format%5Bresources%5D=map&platform=web`).then(
(data) => {
let item = data.data.results?.target ?? []
app.routeView(item)
}
)
}if (kind == "multirooms") {
app.getTypeFromID("multiroom", id, false, { app.getTypeFromID("multiroom", id, false, {
platform: "web", platform: "web",
extend: "editorialArtwork,uber,lockupStyle" extend: "editorialArtwork,uber,lockupStyle"
@ -1935,7 +2057,26 @@ const app = new Vue({
}); });
window.location.hash = `${kind}/${id}` window.location.hash = `${kind}/${id}`
document.querySelector("#app-content").scrollTop = 0 document.querySelector("#app-content").scrollTop = 0
} else if (!kind.toString().includes("radioStation") && !kind.toString().includes("song") && !kind.toString().includes("musicVideo") && !kind.toString().includes("uploadedVideo") && !kind.toString().includes("music-movie")) { } else if (kind.toString().includes("social-profiles")) {
app.page = (kind) + "_" + (id);
app.mk.api.v3.music(
`/v1/social/${app.mk.storefrontId}/social-profiles/${id}`,
{include:"shared-playlists"}).then(
(data) => {
console.log(data)
app.showingPlaylist = data.data?.data[0]
window.location.hash = `${kind}/${id}`
document.querySelector("#app-content").scrollTop = 0
}
)
// app.getTypeFromID((kind), (id), (isLibrary), {
// extend: "editorialVideo",
// include: 'grouping,playlists',
// views: 'top-releases,latest-releases,top-artists'
// });
}
else if (!kind.toString().includes("radioStation") && !kind.toString().includes("song") && !kind.toString().includes("musicVideo") && !kind.toString().includes("uploadedVideo") && !kind.toString().includes("music-movie")) {
let params = { let params = {
extend: "offers,editorialVideo", extend: "offers,editorialVideo",
"views": "appears-on,more-by-artist,related-videos,other-versions,you-might-also-like,video-extras,audio-extras", "views": "appears-on,more-by-artist,related-videos,other-versions,you-might-also-like,video-extras,audio-extras",
@ -1948,7 +2089,7 @@ const app = new Vue({
params["fields[artists]"] = "name,url" params["fields[artists]"] = "name,url"
params["omit[resource]"] = "autos" params["omit[resource]"] = "autos"
params["meta[albums:tracks]"] = 'popularity' params["meta[albums:tracks]"] = 'popularity'
params["fields[albums]"] = "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialNotes,editorialVideo,name,playParams,releaseDate,url,copyright" params["fields[albums]"] = "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialNotes,editorialVideo,name,playParams,releaseDate,url,copyright,genreNames"
} }
if (kind.includes("playlist") || kind.includes("album")) { if (kind.includes("playlist") || kind.includes("album")) {
app.page = (kind) + "_" + (id); app.page = (kind) + "_" + (id);
@ -2005,8 +2146,8 @@ const app = new Vue({
async getNowPlayingItemDetailed(target) { async getNowPlayingItemDetailed(target) {
let nowPlayingItem = JSON.parse(JSON.stringify(this.mk.nowPlayingItem)) let nowPlayingItem = JSON.parse(JSON.stringify(this.mk.nowPlayingItem))
if(nowPlayingItem.type === "radioStation" && app.mk.nowPlayingItem.id !== -1) { if (nowPlayingItem.type === "radioStation" && app.mk.nowPlayingItem.id !== -1) {
nowPlayingItem.playParams = {kind: "songs"} nowPlayingItem.playParams = { kind: "songs" }
nowPlayingItem.attributes.playParams.catalogId = app.mk.nowPlayingItem.id nowPlayingItem.attributes.playParams.catalogId = app.mk.nowPlayingItem.id
nowPlayingItem.attributes.playParams.id = app.mk.nowPlayingItem.id nowPlayingItem.attributes.playParams.id = app.mk.nowPlayingItem.id
nowPlayingItem.id = app.mk.nowPlayingItem.id nowPlayingItem.id = app.mk.nowPlayingItem.id
@ -2193,9 +2334,13 @@ const app = new Vue({
searchLibrarySongs() { searchLibrarySongs() {
let self = this let self = this
let prefs = this.cfg.libraryPrefs.songs let prefs = this.cfg.libraryPrefs.songs
let albumAdded = self.library?.albums?.listing?.map(function (i) {
return { [i.id]: i.attributes?.dateAdded } const albumAdded = {}
})
for (const listing of self.library?.albums?.listing ?? []) {
albumAdded[listing.id] = listing.attributes?.dateAdded
}
let startTime = new Date().getTime() let startTime = new Date().getTime()
function sortSongs() { function sortSongs() {
@ -2207,12 +2352,11 @@ const app = new Vue({
if (prefs.sort == "genre") { if (prefs.sort == "genre") {
aa = a.attributes.genreNames[0] aa = a.attributes.genreNames[0]
bb = b.attributes.genreNames[0] bb = b.attributes.genreNames[0]
} } else if (prefs.sort == "dateAdded") {
if (prefs.sort == "dateAdded") { let albumida = a.relationships?.albums?.data[0]?.id
let albumida = a.relationships?.albums?.data[0]?.id ?? '1970-01-01T00:01:01Z' let albumidb = b.relationships?.albums?.data[0]?.id
let albumidb = b.relationships?.albums?.data[0]?.id ?? '1970-01-01T00:01:01Z' aa = startTime - new Date(albumAdded[albumida] ?? '1970-01-01T00:01:01Z').getTime()
aa = startTime - new Date(((albumAdded.find(i => i[albumida])) ?? [])[albumida] ?? '1970-01-01T00:01:01Z').getTime() bb = startTime - new Date(albumAdded[albumidb] ?? '1970-01-01T00:01:01Z').getTime()
bb = startTime - new Date(((albumAdded.find(i => i[albumidb])) ?? [])[albumidb] ?? '1970-01-01T00:01:01Z').getTime()
} }
if (aa == null) { if (aa == null) {
aa = "" aa = ""
@ -2974,7 +3118,10 @@ const app = new Vue({
const track = encodeURIComponent((this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.title ?? '' : ''); const track = encodeURIComponent((this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.title ?? '' : '');
const artist = encodeURIComponent((this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.artistName ?? '' : ''); const artist = encodeURIComponent((this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.artistName ?? '' : '');
const time = encodeURIComponent((this.mk.nowPlayingItem != null) ? (Math.round((this.mk.nowPlayingItem.attributes["durationInMillis"] ?? -1000) / 1000) ?? -1) : -1); const time = encodeURIComponent((this.mk.nowPlayingItem != null) ? (Math.round((this.mk.nowPlayingItem.attributes["durationInMillis"] ?? -1000) / 1000) ?? -1) : -1);
const id = encodeURIComponent((this.mk.nowPlayingItem != null) ? app.mk.nowPlayingItem._songId ?? (app.mk.nowPlayingItem["songId"] ?? '') : ''); let id = null; let vanity_id = null;
if (this.mk.nowPlayingItem != null && app.mk.nowPlayingItem.localFilesMetadata != null) {const id = encodeURIComponent('')}
else {id = encodeURIComponent((this.mk.nowPlayingItem != null) ? (app.mk.nowPlayingItem._songId) ?? (app.mk.nowPlayingItem["songId"] ?? '') : '');}
let lrcfile = ""; let lrcfile = "";
let richsync = []; let richsync = [];
const lang = app.cfg.lyrics.mxm_language // translation language const lang = app.cfg.lyrics.mxm_language // translation language
@ -2982,67 +3129,13 @@ const app = new Vue({
return Math.random().toString(36).replace(/[^a-z]+/g, '').slice(2, 10); return Math.random().toString(36).replace(/[^a-z]+/g, '').slice(2, 10);
} }
/* get token */
function getToken(mode, track, artist, songid, lang, time, id) { function getMXMSubs(track, artist, lang, time, id) {
if (attempt > 2) { let richsyncQuery = app.cfg.lyrics.mxm_karaoke
app.loadNeteaseLyrics(); let itunesid = (id && id != "") ? id : ''; // Mode 1 -> Subs
// app.loadAMLyrics(); let url = "https://api.cider.sh/v1/lyrics?" + "mode=1" + "&richsyncQuery=" + richsyncQuery + "&track=" + track + "&artist=" + artist + "&songID=" + itunesid + "&source=mxm" + "&lang=" + lang + "&time=" + time;
} else {
attempt = attempt + 1;
let url = "https://apic-desktop.musixmatch.com/ws/1.1/token.get?app_id=web-desktop-app-v1.0&t=" + revisedRandId();
let req = new XMLHttpRequest(); let req = new XMLHttpRequest();
req.overrideMimeType("application/json"); req.overrideMimeType("application/json");
req.open('GET', url, true);
req.setRequestHeader("authority", "apic-desktop.musixmatch.com");
req.onload = function () {
try {
let jsonResponse = JSON.parse(this.responseText);
let status2 = jsonResponse["message"]["header"]["status_code"];
if (status2 == 200) {
let token = jsonResponse["message"]["body"]["user_token"] ?? '';
if (token != "" && token != "UpgradeOnlyUpgradeOnlyUpgradeOnlyUpgradeOnly") {
console.debug('200 token', mode);
// token good
app.mxmtoken = token;
if (mode == 1) {
getMXMSubs(track, artist, app.mxmtoken, lang, time, id);
} else {
getMXMTrans(songid, lang, app.mxmtoken);
}
} else {
console.debug('fake 200 token');
getToken(mode, track, artist, songid, lang, time)
}
} else {
// console.log('token 4xx');
getToken(mode, track, artist, songid, lang, time)
}
} catch (e) {
console.log('error');
app.loadQQLyrics();
//app.loadAMLyrics();
}
};
req.onerror = function () {
console.log('error');
app.loadQQLyrics();
// app.loadAMLyrics();
};
req.send();
}
}
function getMXMSubs(track, artist, token, lang, time, id) {
let usertoken = encodeURIComponent(token);
let richsyncQuery = (app.cfg.lyrics.mxm_karaoke) ? "&optional_calls=track.richsync" : ""
let timecustom = (!time || (time && time < 0)) ? '' : `&f_subtitle_length=${time}&q_duration=${time}&f_subtitle_length_max_deviation=40`;
let itunesid = (id && id != "") ? `&track_itunes_id=${id}` : '';
let url = "https://apic-desktop.musixmatch.com/ws/1.1/macro.subtitles.get?format=json&namespace=lyrics_richsynched" + richsyncQuery + "&subtitle_format=lrc&q_artist=" + artist + "&q_track=" + track + itunesid + "&usertoken=" + usertoken + timecustom + "&app_id=web-desktop-app-v1.0&t=" + revisedRandId();
let req = new XMLHttpRequest();
req.overrideMimeType("application/json");
req.open('GET', url, true);
req.setRequestHeader("authority", "apic-desktop.musixmatch.com");
req.onload = function () { req.onload = function () {
try { try {
let jsonResponse = JSON.parse(this.responseText); let jsonResponse = JSON.parse(this.responseText);
@ -3050,11 +3143,13 @@ const app = new Vue({
let status1 = jsonResponse["message"]["header"]["status_code"]; let status1 = jsonResponse["message"]["header"]["status_code"];
if (status1 == 200) { if (status1 == 200) {
let id = ''; let id, songLang = '';
try { try {
if (jsonResponse["message"]["body"]["macro_calls"]["matcher.track.get"]["message"]["header"]["status_code"] == 200 && jsonResponse["message"]["body"]["macro_calls"]["track.subtitles.get"]["message"]["header"]["status_code"] == 200) { if (jsonResponse["message"]["body"]["macro_calls"]["matcher.track.get"]["message"]["header"]["status_code"] == 200 && jsonResponse["message"]["body"]["macro_calls"]["track.subtitles.get"]["message"]["header"]["status_code"] == 200) {
id = jsonResponse["message"]["body"]["macro_calls"]["matcher.track.get"]["message"]["body"]["track"]["track_id"] ?? ''; id = jsonResponse["message"]["body"]["macro_calls"]["matcher.track.get"]["message"]["body"]["track"]["track_id"] ?? '';
lrcfile = jsonResponse["message"]["body"]["macro_calls"]["track.subtitles.get"]["message"]["body"]["subtitle_list"][0]["subtitle"]["subtitle_body"]; lrcfile = jsonResponse["message"]["body"]["macro_calls"]["track.subtitles.get"]["message"]["body"]["subtitle_list"][0]["subtitle"]["subtitle_body"];
vanity_id = jsonResponse["message"]["body"]["macro_calls"]["matcher.track.get"]["message"]["body"]["track"]["commontrack_vanity_id"];
songLang = jsonResponse["message"]["body"]["macro_calls"]["track.lyrics.get"]["message"]["body"]["lyrics"]["lyrics_language_description"];
try { try {
let lrcrich = jsonResponse["message"]["body"]["macro_calls"]["track.richsync.get"]["message"]["body"]["richsync"]["richsync_body"]; let lrcrich = jsonResponse["message"]["body"]["macro_calls"]["track.richsync.get"]["message"]["body"]["richsync"]["richsync_body"];
@ -3069,7 +3164,7 @@ const app = new Vue({
// app.loadAMLyrics() // app.loadAMLyrics()
} else { } else {
if (richsync == [] || richsync.length == 0) { if (richsync == [] || richsync.length == 0) {
console.log("ok"); console.log("musixmatch worki");
// process lrcfile to json here // process lrcfile to json here
app.lyricsMediaItem = lrcfile app.lyricsMediaItem = lrcfile
let u = app.lyricsMediaItem.split(/[\r\n]/); let u = app.lyricsMediaItem.split(/[\r\n]/);
@ -3110,24 +3205,21 @@ const app = new Vue({
}); });
app.lyrics = preLrc; app.lyrics = preLrc;
} }
if (lrcfile != null && lrcfile != '') {
// load translation // Load translation
getMXMTrans(id, lang, token); if (songLang.toLowerCase() !== lang){
} else { getMXMTrans(lang, vanity_id);
// app.loadAMLyrics()
app.loadQQLyrics();
} }
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);
app.loadQQLyrics(); app.loadQQLyrics();
// app.loadAMLyrics() // app.loadAMLyrics()
} }
} else { //4xx rejected
getToken(1, track, artist, '', lang, time);
} }
} catch (e) { } catch (e) {
console.log(e); console.error(e);
app.loadQQLyrics(); app.loadQQLyrics();
//app.loadAMLyrics() //app.loadAMLyrics()
} }
@ -3137,59 +3229,57 @@ const app = new Vue({
console.log('error'); console.log('error');
// app.loadAMLyrics(); // app.loadAMLyrics();
}; };
req.open('POST', url, true);
req.send(); req.send();
} }
function getMXMTrans(id, lang, token) { function getMXMTrans(lang, vanity_id) {
if (lang != "disabled" && id != '') {
let usertoken = encodeURIComponent(token);
let url2 = "https://apic-desktop.musixmatch.com/ws/1.1/crowd.track.translations.get?translation_fields_set=minimal&selected_language=" + lang + "&track_id=" + id + "&comment_format=text&part=user&format=json&usertoken=" + usertoken + "&app_id=web-desktop-app-v1.0&t=" + revisedRandId();
let req2 = new XMLHttpRequest();
req2.overrideMimeType("application/json");
req2.open('GET', url2, true);
req2.setRequestHeader("authority", "apic-desktop.musixmatch.com");
req2.onload = function () {
try { try {
let jsonResponse2 = JSON.parse(this.responseText); if (lang !== "disabled" && vanity_id !== '') { // Mode 2 -> Trans
console.log(jsonResponse2); let url = "https://api.cider.sh/v1/lyrics?mode=2&vanityID=" + vanity_id +'&source=mxm&lang=' + lang;
let status2 = jsonResponse2["message"]["header"]["status_code"]; let req = new XMLHttpRequest();
if (status2 == 200) { req.overrideMimeType("application/json");
try { req.onload = function () {
let preTrans = [] if (req.status == 200) { // If it's not 200, 237890127389012 things could go wrong and I don't really care what those things are.
let u = app.lyrics; let jsonResponse = JSON.parse(this.responseText);
let translation_list = jsonResponse2["message"]["body"]["translations_list"]; let applied = 0;
if (translation_list.length > 0) { for (let i = 0; applied < app.lyrics.length; i++) {
for (var i = 0; i < u.length - 1; i++) { if (app.lyrics[applied].line.trim() === "") {applied+=1;}
preTrans[i] = "" if (app.lyrics[applied].line.trim() === jsonResponse[i]) {
for (var trans_line of translation_list) { // Do Nothing
if (u[i].line == " " + trans_line["translation"]["matched_line"] || u[i].line == trans_line["translation"]["matched_line"]) { applied +=1;
u[i].translation = trans_line["translation"]["description"]; }
break; else {
if (app.lyrics[applied].line === "lrcInstrumental") {
if (app.lyrics[applied+1].line.trim() === jsonResponse[i]) {
// Do Nothing
applied +=2;
}
else {
app.lyrics[applied+1].translation = jsonResponse[i];
applied +=2;
}
}
else {
app.lyrics[applied].translation = jsonResponse[i];
applied +=1;
} }
} }
} }
app.lyrics = u;
}
} catch (e) {
/// not found trans -> ignore
}
} else { //4xx rejected
getToken(2, '', '', id, lang, '');
}
} catch (e) {
} }
} }
req2.send(); req.onerror = function () {
console.log("MXM Translation somehow died. Don't need to know why.")
};
req.open('POST', url, true);
req.send();
} }
} catch (e) {console.debug("Error while parsing MXM Trans: " + e)}
} }
if (track != "" & track != "No Title Found") { if (track != "" & track != "No Title Found") {
if (app.mxmtoken != null && app.mxmtoken != '') { getMXMSubs(track, artist, lang, time, id);
getMXMSubs(track, artist, app.mxmtoken, lang, time, id)
} else {
getToken(1, track, artist, '', lang, time);
}
} }
}, },
loadNeteaseLyrics() { loadNeteaseLyrics() {
@ -3308,6 +3398,7 @@ const app = new Vue({
translation: '' translation: ''
}); });
app.lyrics = preLrc.reverse(); app.lyrics = preLrc.reverse();
if (app.lyrics[5].line == "") {app.loadNeteaseLyrics();} // Detect incomplete QQ lyrics.
} catch (e) { } catch (e) {
console.log(e) console.log(e)
app.loadNeteaseLyrics(); app.loadNeteaseLyrics();
@ -3473,7 +3564,15 @@ const app = new Vue({
console.log(truekind, id) console.log(truekind, id)
try { try {
if (app.library.songs.displayListing.length > childIndex && parent == "librarysongs") { if (parent == 'playlist:ciderlocal'){
let u = app.library.localsongs.map(i => {return i.id})
app.mk.setQueue({"episodes" : u}).then(()=>{
let id = app.mk.queue._itemIDs.findIndex(element => element == item.id);
app.mk.changeToMediaAtIndex(id)
})
}
else if (app.library.songs.displayListing.length > childIndex && parent == "librarysongs") {
console.log(item) console.log(item)
if (item && ((app.library.songs.displayListing[childIndex].id != item.id))) { if (item && ((app.library.songs.displayListing[childIndex].id != item.id))) {
childIndex = app.library.songs.displayListing.indexOf(item) childIndex = app.library.songs.displayListing.indexOf(item)
@ -3777,7 +3876,7 @@ const app = new Vue({
type += "s" type += "s"
} }
type = type.replace("library-", "") type = type.replace("library-", "")
let id = item.attributes.playParams.catalogId ?? item.attributes.playParams.id ?? item.id let id = item.attributes.playParams?.catalogId ?? item.attributes.playParams.id ?? item.id
let index = types.findIndex(function (type) { let index = types.findIndex(function (type) {
return type.type == this return type.type == this
@ -3923,9 +4022,8 @@ const app = new Vue({
this.currentArtUrl = this.mk.nowPlayingItem._assets[0].artworkURL this.currentArtUrl = this.mk.nowPlayingItem._assets[0].artworkURL
} }
try { try {
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); // document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
} catch (e) { } catch (e) {}
}
} else { } else {
let data = await this.mk.api.v3.music(`/v1/me/library/songs/${this.mk.nowPlayingItem.id}`); let data = await this.mk.api.v3.music(`/v1/me/library/songs/${this.mk.nowPlayingItem.id}`);
data = data.data.data[0]; data = data.data.data[0];
@ -3937,14 +4035,14 @@ const app = new Vue({
} }
ipcRenderer.send('updateRPCImage', this.currentArtUrl ?? ''); ipcRenderer.send('updateRPCImage', this.currentArtUrl ?? '');
try { try {
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); // document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
} catch (e) { } catch (e) {
} }
} else { } else {
this.currentArtUrlRaw = '' this.currentArtUrlRaw = ''
this.currentArtUrl = ''; this.currentArtUrl = '';
try { try {
document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); // document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
} catch (e) { } catch (e) {
} }
} }
@ -4318,7 +4416,7 @@ const app = new Vue({
"name": app.getLz('settings.option.audio.audioLab'), "name": app.getLz('settings.option.audio.audioLab'),
"hidden": true, "hidden": true,
"action": function () { "action": function () {
app.appRoute('audiolabs') app.openSettingsPage('audiolabs')
} }
}, },
] ]
@ -4344,7 +4442,7 @@ const app = new Vue({
try { try {
// if its a radio station, then change the attributes to match a song // if its a radio station, then change the attributes to match a song
const nowPlayingItem = JSON.parse(JSON.stringify(this.mk.nowPlayingItem)) const nowPlayingItem = JSON.parse(JSON.stringify(this.mk.nowPlayingItem))
if(nowPlayingItem.type == "radioStation" && app.mk.nowPlayingItem.id != -1) { if (nowPlayingItem.type == "radioStation" && app.mk.nowPlayingItem.id != -1) {
nowPlayingItem.type = "song" nowPlayingItem.type = "song"
nowPlayingItem.attributes.playParams.catalogId = app.mk.nowPlayingItem.id nowPlayingItem.attributes.playParams.catalogId = app.mk.nowPlayingItem.id
nowPlayingItem.attributes.playParams.id = app.mk.nowPlayingItem.id nowPlayingItem.attributes.playParams.id = app.mk.nowPlayingItem.id
@ -4377,36 +4475,43 @@ const app = new Vue({
} }
}, },
LastFMDeauthorize() { openSettingsPage(page) {
ipcRenderer.invoke('setStoreValue', 'lastfm.enabled', false).catch((e) => console.error(e)); switch (page) {
ipcRenderer.invoke('setStoreValue', 'lastfm.auth_token', '').catch((e) => console.error(e)); case "general":
app.cfg.lastfm.auth_token = ""; this.$store.state.pageState.settings.currentTabIndex = 0
app.cfg.lastfm.enabled = false; break;
const element = document.getElementById('lfmConnect'); case "audio":
element.innerHTML = app.getLz('term.connect'); this.$store.state.pageState.settings.currentTabIndex = 1
element.onclick = app.LastFMAuthenticate; break;
}, case "audiolabs":
LastFMAuthenticate() { this.$store.state.pageState.settings.currentTabIndex = 2
console.log("[LastFM] Received LastFM authentication callback") break;
const element = document.getElementById('lfmConnect'); case "styles":
// new key : f9986d12aab5a0fe66193c559435ede3 this.$store.state.pageState.settings.currentTabIndex = 3
window.open('https://www.last.fm/api/auth?api_key=f9986d12aab5a0fe66193c559435ede3&cb=cider://auth/lastfm'); break;
element.innerText = app.getLz('term.connecting') + '...'; case "visual":
this.$store.state.pageState.settings.currentTabIndex = 4
/* Just a timeout for the button */ break;
setTimeout(() => { case "github-plugins":
if (element.innerText === app.getLz('term.connecting') + '...') { this.$store.state.pageState.settings.currentTabIndex = 5
element.innerText = app.getLz('term.connect'); break;
console.warn('[LastFM] Attempted connection timed out.'); case "lyrics":
this.$store.state.pageState.settings.currentTabIndex = 6
break;
case "connectivity":
this.$store.state.pageState.settings.currentTabIndex = 7
break;
case "advanced":
this.$store.state.pageState.settings.currentTabIndex = 8
break;
case "keybindings":
this.$store.state.pageState.settings.currentTabIndex = 9
break;
case "github-themes":
this.$store.state.pageState.settings.currentTabIndex = 10
break;
} }
}, 20000); app.modals.settings = true
ipcRenderer.on('LastfmAuthenticated', function (_event, lfmAuthKey) {
app.cfg.lastfm.auth_token = lfmAuthKey;
app.cfg.lastfm.enabled = true;
element.innerHTML = `${app.getLz('term.disconnect')}\n<p style="font-size: 8px"><i>(${app.getLz('term.authed')}: ${lfmAuthKey})</i></p>`;
element.onclick = app.LastFMDeauthorize;
});
}, },
fullscreen(flag) { fullscreen(flag) {
this.fullscreenState = flag; this.fullscreenState = flag;
@ -4619,6 +4724,29 @@ const app = new Vue({
app.mk._services.mediaItemPlayback._currentPlayer._playAssetURL(src, false) app.mk._services.mediaItemPlayback._currentPlayer._playAssetURL(src, false)
} }
} }
},
confirm(message, callback) {
bootbox.confirm(this.getBootboxParams(null, message, callback));
},
prompt(title, callback) {
bootbox.prompt(this.getBootboxParams(title, null, callback));
},
getBootboxParams(title, message, callback) {
return {
title: title,
message: message,
buttons: {
confirm: {
label: app.getLz('dialog.ok'),
},
cancel: {
label: app.getLz('dialog.cancel'),
},
},
callback: function (result) {
if (callback) callback(result);
},
}
} }
} }
}) })

View file

@ -1,5 +1,6 @@
const store = new Vuex.Store({ const store = new Vuex.Store({
state: { state: {
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"),
@ -12,6 +13,10 @@ const store = new Vuex.Store({
nextUrl: null, nextUrl: null,
items: [], items: [],
size: "normal" size: "normal"
},
settings: {
currentTabIndex: 0,
fullscreen: false
} }
}, },
artwork: { artwork: {

File diff suppressed because it is too large Load diff

View file

@ -1,42 +0,0 @@
<div id="app-content" :scrollpos="chrome.contentScrollPosY" scrollaxis="y" :style="{'overflow': (chrome.contentAreaScrolling ? '' : 'hidden')}">
<div id="navigation-bar" v-if="getThemeDirective('appNavigation') == 'seperate'">
<button class="nav-item" @click="navigateBack()">
<%- include('../svg/chevron-left.svg') %>
</button>
<button class="nav-item" @click="navigateForward()">
<%- include('../svg/chevron-right.svg') %>
</button>
</div>
<!-- Include App Routes -->
<% for(var i=0; i < Object.keys(env.appRoutes).length ; i++) {%>
<transition
<% if(env.appRoutes[i].onEnter) {
%>
v-on:enter="<%- env.appRoutes[i].onEnter %>"
<%
}
%>
:name="chrome.desiredPageTransition">
<template
v-if="<%- env.appRoutes[i].condition %>">
<%- env.appRoutes[i].component %>
</template>
</transition>
<% } %>
<!-- Library - Made For You -->
<transition :name="chrome.desiredPageTransition" v-on:enter="getMadeForYou()">
<template v-if="page == 'library-madeforyou'">
<%- include('../pages/madeforyou') %>');
%>
</template>
</transition>
<!-- Library - Artists-->
<!-- Keybinds -->
<transition name="wpfade">
<template v-if="page == 'keybinds-settings'">
<keybinds-settings></keybinds-settings>
</template>
</transition>
</div>

View file

@ -65,7 +65,7 @@
</button> </button>
<button <button
class="usermenu-item" class="usermenu-item"
@click="cfg.advanced.AudioContext ? modals.castMenu = true : $root.notyf.error($root.getLz('settings.warn.enableAdvancedFunctionality'))" @click="cfg.advanced.AudioContext ? modals.castMenu = true :(cfg.advanced.AudioContext = true, modals.castMenu = true)"
> >
<span class="usermenu-item-icon"> <span class="usermenu-item-icon">
<%- include("../svg/cast.svg") %> <%- include("../svg/cast.svg") %>
@ -76,7 +76,7 @@
</button> </button>
<button <button
class="usermenu-item" class="usermenu-item"
@click="cfg.advanced.AudioContext ? modals.audioSettings = true : $root.notyf.error($root.getLz('settings.warn.enableAdvancedFunctionality'))" @click="cfg.advanced.AudioContext ? modals.audioSettings = true : (cfg.advanced.AudioContext = true, modals.audioSettings = true)"
> >
<span class="usermenu-item-icon"> <span class="usermenu-item-icon">
<%- include("../svg/headphones.svg") %> <%- include("../svg/headphones.svg") %>
@ -105,7 +105,7 @@
$root.getLz("term.about") $root.getLz("term.about")
}}</span> }}</span>
</button> </button>
<button class="usermenu-item" @click="appRoute('settings')"> <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>
@ -121,13 +121,21 @@
$root.getLz("term.logout") $root.getLz("term.logout")
}}</span> }}</span>
</button> </button>
<button class="usermenu-item" @click="quit()">
<span class="usermenu-item-icon" style="right: 2.5px">
<%- include("../svg/x.svg") %>
</span>
<span class="usermenu-item-name">{{
$root.getLz("term.quit")
}}</span>
</button>
</div> </div>
</div> </div>
</transition> </transition>
<transition name="sidebartransition"> <transition name="sidebartransition">
<%- include("sidebar") %> <cider-app-sidebar v-if="!chrome.sidebarCollapsed"></cider-app-sidebar>
</transition> </transition>
<%- include("app-content") %> <app-content-area></app-content-area>
<transition name="drawertransition"> <transition name="drawertransition">
<div class="app-drawer" <div class="app-drawer"
v-if="drawer.open && drawer.panel == 'lyrics' && lyrics && lyrics != [] && lyrics.length > 0"> v-if="drawer.open && drawer.panel == 'lyrics' && lyrics && lyrics != [] && lyrics.length > 0">
@ -138,7 +146,7 @@
<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 no-animation" :src="$store.state.artwork.playerLCD">
</div> </div>
</div> </div>
<lyrics-view v-if="drawer.panel == 'lyrics'" :time="lyriccurrenttime" :lyrics="lyrics" <lyrics-view v-if="drawer.panel == 'lyrics'" :time="mk.currentPlaybackTime - lyricOffset" :lyrics="lyrics"
:richlyrics="richlyrics"></lyrics-view> :richlyrics="richlyrics"></lyrics-view>
<div v-if="drawer.panel == 'lyrics'" class="lyric-footer"> <div v-if="drawer.panel == 'lyrics'" class="lyric-footer">
<button class="md-btn" @click="modularUITest(!fullscreenLyrics)">{{fullscreenLyrics ? <button class="md-btn" @click="modularUITest(!fullscreenLyrics)">{{fullscreenLyrics ?

View file

@ -5,7 +5,7 @@
<div class="app-playback-controls" @mouseover="chrome.progresshover = true" <div class="app-playback-controls" @mouseover="chrome.progresshover = true"
@mouseleave="chrome.progresshover = false" @contextmenu="nowPlayingContextMenu"> @mouseleave="chrome.progresshover = false" @contextmenu="nowPlayingContextMenu">
<div class="artwork" id="artworkLCD"> <div class="artwork" id="artworkLCD">
<mediaitem-artwork :url="currentArtUrl"></mediaitem-artwork> <mediaitem-artwork :url="$root.currentArtUrl"></mediaitem-artwork>
</div> </div>
<b-popover custom-class="mediainfo-popover" target="artworkLCD" triggers="hover" placement="right"> <b-popover custom-class="mediainfo-popover" target="artworkLCD" triggers="hover" placement="right">
<div class="content"> <div class="content">
@ -45,7 +45,15 @@
</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 ppe-icon" v-if="cfg.audio.maikiwiAudio.ciderPPE === true"></div> <div class="audio-type spatial-icon" v-if="cfg.audio.maikiwiAudio.spatial === true"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization') + ' (' + getProfileLz('CTS', cfg.audio.maikiwiAudio.spatialProfile) + ')'" v-b-tooltip.hover
></div>
<div class="audio-type lossless-icon" v-if="(mk.nowPlayingItem?.localFilesMetadata?.lossless ?? false) === true"
:title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container" v-b-tooltip.hover
></div>
<div class="audio-type ppe-icon" v-if="mk.nowPlayingItem.localFilesMetadata == null && cfg.audio.maikiwiAudio.ciderPPE === true"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')" v-b-tooltip.hover
></div>
</div> </div>
</div> </div>
<template v-if="mk.nowPlayingItem['attributes']['playParams']"> <template v-if="mk.nowPlayingItem['attributes']['playParams']">
@ -144,7 +152,7 @@
<button class="playback-button--small cast" <button class="playback-button--small cast"
:title="$root.getLz('term.cast')" :title="$root.getLz('term.cast')"
v-b-tooltip.hover v-b-tooltip.hover
@click="cfg.advanced.AudioContext ? modals.castMenu = true : $root.notyf.error($root.getLz('settings.warn.enableAdvancedFunctionality'))"></button> @click="cfg.advanced.AudioContext ? modals.castMenu = true : (cfg.advanced.AudioContext = true, 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'}"

View file

@ -9,8 +9,8 @@
</div> </div>
</div> </div>
<div class="app-chrome-item full-height" v-else> <div class="app-chrome-item full-height" v-else>
<button class="app-mainmenu" @blur="mainMenuVisibility(false, true)" @click="mainMenuVisibility(true, false)" <button class="app-mainmenu" @blur="mainMenuVisibility(false)" @click="mainMenuVisibility(true)"
@contextmenu="mainMenuVisibility(true, true)" :class="{active: chrome.menuOpened}" @contextmenu="mainMenuVisibility(true)" :class="{active: chrome.menuOpened}"
:aria-label="$root.getLz('term.quickNav')"></button> :aria-label="$root.getLz('term.quickNav')"></button>
</div> </div>
<template v-if="getThemeDirective('appNavigation') != 'seperate'"> <template v-if="getThemeDirective('appNavigation') != 'seperate'">
@ -18,13 +18,13 @@
<div class="app-chrome-item"> <div class="app-chrome-item">
<button class="playback-button navigation" @click="navigateBack()" :title="$root.getLz('term.navigateBack')" <button class="playback-button navigation" @click="navigateBack()" :title="$root.getLz('term.navigateBack')"
v-b-tooltip.hover> v-b-tooltip.hover>
<%- include('../svg/chevron-left.svg') %> <svg-icon url="./views/svg/chevron-left.svg"></svg-icon>
</button> </button>
</div> </div>
<div class="app-chrome-item"> <div class="app-chrome-item">
<button class="playback-button navigation" @click="navigateForward()" <button class="playback-button navigation" @click="navigateForward()"
:title="$root.getLz('term.navigateForward')" v-b-tooltip.hover> :title="$root.getLz('term.navigateForward')" v-b-tooltip.hover>
<%- include('../svg/chevron-right.svg') %> <svg-icon url="./views/svg/chevron-right.svg"></svg-icon>
</button> </button>
</div> </div>
<div class="app-chrome-item" v-if="getThemeDirective('windowLayout') == 'twopanel'"> <div class="app-chrome-item" v-if="getThemeDirective('windowLayout') == 'twopanel'">
@ -43,6 +43,7 @@
<div class="vdiv display--large" v-if="getThemeDirective('windowLayout') != 'twopanel'"></div> <div class="vdiv display--large" v-if="getThemeDirective('windowLayout') != 'twopanel'"></div>
</template> </template>
<template v-if="getThemeDirective('windowLayout') != 'twopanel'"> <template v-if="getThemeDirective('windowLayout') != 'twopanel'">
<div class="app-chrome-item playback-control-buttons">
<div class="app-chrome-item display--large"> <div class="app-chrome-item display--large">
<button class="playback-button--small shuffle" v-if="mk.shuffleMode == 0" :class="isDisabled() && 'disabled'" <button class="playback-button--small shuffle" v-if="mk.shuffleMode == 0" :class="isDisabled() && 'disabled'"
@click="mk.shuffleMode = 1" :title="$root.getLz('term.enableShuffle')" v-b-tooltip.hover></button> @click="mk.shuffleMode = 1" :title="$root.getLz('term.enableShuffle')" v-b-tooltip.hover></button>
@ -76,6 +77,7 @@
:class="isDisabled() && 'disabled'" v-else-if="mk.repeatMode == 2" :title="$root.getLz('term.disableRepeat')" :class="isDisabled() && 'disabled'" v-else-if="mk.repeatMode == 2" :title="$root.getLz('term.disableRepeat')"
v-b-tooltip.hover></button> v-b-tooltip.hover></button>
</div> </div>
</div>
</template> </template>
</div> </div>
<div class="app-chrome--center"> <div class="app-chrome--center">
@ -121,7 +123,15 @@
<div class="playback-info"> <div class="playback-info">
<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 ppe-icon" v-if="cfg.audio.maikiwiAudio.ciderPPE === true"></div> <div class="audio-type spatial-icon" v-if="cfg.audio.maikiwiAudio.spatial === true"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization') + ' (' + getProfileLz('CTS', cfg.audio.maikiwiAudio.spatialProfile) + ')'" v-b-tooltip.hover
></div>
<div class="audio-type lossless-icon" v-if="(mk.nowPlayingItem?.localFilesMetadata?.lossless ?? false) === true"
:title="mk.nowPlayingItem?.localFilesMetadata?.bitDepth +'-bit / '+ mk.nowPlayingItem?.localFilesMetadata?.sampleRate/1000 + ' kHz ' + mk.nowPlayingItem.localFilesMetadata.container" v-b-tooltip.hover
></div>
<div class="audio-type ppe-icon" v-if="mk.nowPlayingItem.localFilesMetadata == null && cfg.audio.maikiwiAudio.ciderPPE === true"
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')" v-b-tooltip.hover
></div>
</div> </div>
<div class="info-rect"> <div class="info-rect">
<div class="song-name" <div class="song-name"
@ -198,13 +208,13 @@
</div> </div>
<div class="app-chrome-item" v-else> <div class="app-chrome-item" v-else>
<div class="top-nav-group"> <div class="top-nav-group">
<sidebar-library-item :name="$root.getLz('home.title')" svg-icon="./assets/feather/home.svg" page="home"> <sidebar-library-item :name="$root.getLz('home.title')" svg-icon="./assets/feather/home.svg" svg-icon-name="home" page="home">
</sidebar-library-item> </sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.listenNow')" svg-icon="./assets/feather/play-circle.svg" <sidebar-library-item :name="$root.getLz('term.listenNow')" svg-icon="./assets/feather/play-circle.svg" svg-icon-name="listenNow"
page="listen_now"></sidebar-library-item> page="listen_now"></sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.browse')" svg-icon="./assets/feather/globe.svg" page="browse"> <sidebar-library-item :name="$root.getLz('term.browse')" svg-icon="./assets/feather/globe.svg" svg-icon-name="browse" page="browse">
</sidebar-library-item> </sidebar-library-item>
<sidebar-library-item :name="$root.getLz('term.radio')" svg-icon="./assets/feather/radio.svg" page="radio"> <sidebar-library-item :name="$root.getLz('term.radio')" svg-icon="./assets/feather/radio.svg" svg-icon-name="radio" page="radio">
</sidebar-library-item> </sidebar-library-item>
</div> </div>
</div> </div>
@ -221,7 +231,7 @@
</div> </div>
<div class="app-chrome-item generic"> <div class="app-chrome-item generic">
<button class="playback-button--small cast" :title="$root.getLz('term.cast')" <button class="playback-button--small cast" :title="$root.getLz('term.cast')"
@click="cfg.advanced.AudioContext ? modals.castMenu = true : $root.notyf.error($root.getLz('settings.warn.enableAdvancedFunctionality'))" @click="cfg.advanced.AudioContext ? modals.castMenu = true : (cfg.advanced.AudioContext = true, modals.castMenu = true)"
v-b-tooltip.hover></button> v-b-tooltip.hover></button>
</div> </div>
<div class="app-chrome-item generic"> <div class="app-chrome-item generic">
@ -269,8 +279,8 @@
</div> </div>
</div> </div>
<div class="app-chrome-item full-height" v-else-if="platform != 'darwin' && !chrome.nativeControls"> <div class="app-chrome-item full-height" v-else-if="platform != 'darwin' && !chrome.nativeControls">
<button class="app-mainmenu" @blur="mainMenuVisibility(false, true)" @click="mainMenuVisibility(true, false)" <button class="app-mainmenu" @blur="mainMenuVisibility(false)" @click="mainMenuVisibility(true)"
@contextmenu="mainMenuVisibility(true, true)" :class="{active: chrome.menuOpened}"></button> @contextmenu="mainMenuVisibility(true)" :class="{active: chrome.menuOpened}"></button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -25,12 +25,18 @@
<transition name="modal"> <transition name="modal">
<castmenu v-if="modals.castMenu"></castmenu> <castmenu v-if="modals.castMenu"></castmenu>
</transition> </transition>
<transition name="modal">
<pathmenu v-if="modals.pathMenu"></pathmenu>
</transition>
<transition name="modal"> <transition name="modal">
<airplay-modal v-if="modals.airplayPW"></airplay-modal> <airplay-modal v-if="modals.airplayPW"></airplay-modal>
</transition> </transition>
<transition name="modal"> <transition name="modal">
<plugin-menu v-if="modals.pluginMenu"></plugin-menu> <plugin-menu v-if="modals.pluginMenu"></plugin-menu>
</transition> </transition>
<transition name="modal">
<settings-window v-if="modals.settings"></settings-window>
</transition>
<transition name="modal"> <transition name="modal">
<eq-view v-if="modals.equalizer"></eq-view> <eq-view v-if="modals.equalizer"></eq-view>
</transition> </transition>

View file

@ -1,303 +0,0 @@
<div id="app-sidebar" v-if="!chrome.sidebarCollapsed">
<template >
<template v-if="getThemeDirective('windowLayout') != 'twopanel'">
<div class="app-sidebar-header">
<div class="search-input-container">
<div class="search-input--icon"></div>
<input
type="search"
spellcheck="false"
@click="showSearch()"
@focus="search.showHints = true"
@blur="setTimeout(()=>{search.showHints = false}, 300)"
v-on:keyup.enter="searchQuery();search.showHints = false"
@change="showSearch();"
@input="getSearchHints()"
:placeholder="$root.getLz('term.search') + '...'"
v-model="search.term"
ref="searchInput"
class="search-input"
/>
<div
class="search-hints-container"
v-if="search.showHints && search.hints.length != 0"
>
<div class="search-hints">
<button
class="search-hint text-overflow-elipsis"
v-for="hint in search.hints"
@click="search.term = hint;search.showHints = false;searchQuery(hint)"
>
{{ hint }}
</button>
</div>
</div>
</div>
</div>
</template>
<div class="app-sidebar-content" scrollaxis="y">
<!-- AM Navigation -->
<template v-if="getThemeDirective('windowLayout') != 'twopanel'">
<div
class="app-sidebar-header-text"
@click="cfg.general.sidebarCollapsed.cider = !cfg.general.sidebarCollapsed.cider"
:class="{collapsed: cfg.general.sidebarCollapsed.cider}"
>
{{ $root.getLz("app.name") }}
</div>
<template v-if="!cfg.general.sidebarCollapsed.cider">
<sidebar-library-item
:name="$root.getLz('home.title')"
svg-icon="./assets/feather/home.svg"
page="home"
>
</sidebar-library-item>
</template>
<div
class="app-sidebar-header-text"
@click="cfg.general.sidebarCollapsed.applemusic = !cfg.general.sidebarCollapsed.applemusic"
:class="{collapsed: cfg.general.sidebarCollapsed.applemusic}"
>
{{ $root.getLz("term.appleMusic") }}
</div>
<template v-if="!cfg.general.sidebarCollapsed.applemusic">
<sidebar-library-item
:name="$root.getLz('term.listenNow')"
svg-icon="./assets/feather/play-circle.svg"
page="listen_now"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.browse')"
svg-icon="./assets/feather/globe.svg"
page="browse"
>
</sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.radio')"
svg-icon="./assets/feather/radio.svg"
page="radio"
></sidebar-library-item>
</template>
</template>
<div
class="app-sidebar-header-text"
@click="cfg.general.sidebarCollapsed.library = !cfg.general.sidebarCollapsed.library"
:class="{collapsed: cfg.general.sidebarCollapsed.library}"
>
{{ $root.getLz("term.library") }}
</div>
<template v-if="!cfg.general.sidebarCollapsed.library">
<sidebar-library-item
:name="$root.getLz('term.recentlyAdded')"
svg-icon="./assets/feather/plus-circle.svg"
v-if="cfg.general.sidebarItems.recentlyAdded"
page="library-recentlyadded"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.songs')"
svg-icon="./assets/feather/music.svg"
v-if="cfg.general.sidebarItems.songs"
page="library-songs"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.albums')"
svg-icon="./assets/feather/disc.svg"
v-if="cfg.general.sidebarItems.albums"
page="library-albums"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.artists')"
svg-icon="./assets/feather/user.svg"
v-if="cfg.general.sidebarItems.artists"
page="library-artists"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.videos')"
svg-icon="./assets/feather/video.svg"
v-if="cfg.general.sidebarItems.videos"
page="library-videos"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.podcasts')"
svg-icon="./assets/feather/mic.svg"
v-if="cfg.general.sidebarItems.podcasts"
page="podcasts"
>
</sidebar-library-item>
</template>
<template v-if="getPlaylistFolderChildren('p.applemusic').length != 0">
<div
class="app-sidebar-header-text"
@click="cfg.general.sidebarCollapsed.amplaylists = !cfg.general.sidebarCollapsed.amplaylists"
@contextmenu="playlistHeaderContextMenu"
:class="{collapsed: cfg.general.sidebarCollapsed.amplaylists}"
>
{{ $root.getLz("term.appleMusic") }}
{{ $root.getLz("term.playlists") }}
</div>
<template v-if="!cfg.general.sidebarCollapsed.amplaylists">
<sidebar-playlist
v-for="item in getPlaylistFolderChildren('p.applemusic')"
:item="item"
>
</sidebar-playlist>
</template>
</template>
<div
class="app-sidebar-header-text"
@click="cfg.general.sidebarCollapsed.playlists = !cfg.general.sidebarCollapsed.playlists"
@contextmenu="playlistHeaderContextMenu"
:class="{collapsed: cfg.general.sidebarCollapsed.playlists}"
>
{{ $root.getLz("term.playlists") }}
</div>
<template v-if="!cfg.general.sidebarCollapsed.playlists">
<button class="app-sidebar-item" @click="playlistHeaderContextMenu">
<div class="sidebar-icon">
<svg
width="46"
height="46"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 5v14"></path>
<path d="M5 12h14"></path>
</svg>
</div>
{{ getLz("action.createNew") }}
</button>
<sidebar-playlist
v-for="item in getPlaylistFolderChildren('p.playlistsroot')"
:item="item"
>
</sidebar-playlist>
</template>
</div>
<div class="app-sidebar-footer display--small app-sidebar-footer--controls">
<div
class="app-playback-controls"
v-if="mkReady()"
@contextmenu="nowPlayingContextMenu"
>
<div class="control-buttons">
<div class="app-chrome-item">
<button
class="playback-button--small shuffle"
v-if="mk.shuffleMode == 0"
@click="mk.shuffleMode = 1"
:title="$root.getLz('term.enableShuffle')"
:class="$root.isDisabled() && 'disabled'"
v-b-tooltip.hover.righttop
></button>
<button
class="playback-button--small shuffle active"
v-else
@click="mk.shuffleMode = 0"
:title="$root.getLz('term.disableShuffle')"
:class="$root.isDisabled() && 'disabled'"
v-b-tooltip.hover.righttop
></button>
</div>
<div class="app-chrome-item">
<button
class="playback-button previous"
@click="prevButton()"
:class="$root.isPrevDisabled() && 'disabled'"
:title="$root.getLz('term.previous')"
v-b-tooltip.hover
></button>
</div>
<div class="app-chrome-item">
<button class="playback-button stop" @click="$root.mk.stop()"
v-if="$root.mk.isPlaying && $root.mk.nowPlayingItem.attributes.playParams.kind == 'radioStation'"
:title="$root.getLz('term.stop')" v-b-tooltip.hover></button>
<button class="playback-button pause" @click="$root.mk.pause()" v-else-if="$root.mk.isPlaying"
:title="$root.getLz('term.pause')" v-b-tooltip.hover></button>
<button class="playback-button play" @click="$root.mk.play()" v-else :title="$root.getLz('term.play')"
v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item">
<button
class="playback-button next"
@click="skipToNextItem()"
:title="$root.getLz('term.next')"
:class="$root.isNextDisabled() && 'disabled'"
v-b-tooltip.hover
></button>
</div>
<div class="app-chrome-item">
<button
class="playback-button--small repeat"
v-if="mk.repeatMode == 0"
@click="mk.repeatMode = 1"
:class="$root.isDisabled() && 'disabled'"
:title="$root.getLz('term.enableRepeatOne')"
v-b-tooltip.hover
></button>
<button
class="playback-button--small repeat repeatOne"
@click="mk.repeatMode = 2"
v-else-if="mk.repeatMode == 1"
:title="$root.getLz('term.disableRepeatOne')"
:class="$root.isDisabled() && 'disabled'"
v-b-tooltip.hover
></button>
<button
class="playback-button--small repeat active"
@click="mk.repeatMode = 0"
v-else-if="mk.repeatMode == 2"
:title="$root.getLz('term.disableRepeat')"
:class="$root.isDisabled() && 'disabled'"
v-b-tooltip.hover
></button>
</div>
</div>
<div class="app-chrome-item volume">
<div class="input-container">
<button
class="volume-button--small volume"
@click="muteButtonPressed()"
:class="{'active': this.cfg.audio.volume == 0}"
:title="cfg.audio.muted ? $root.getLz('term.unmute') : $root.getLz('term.mute')"
v-b-tooltip.hover
></button>
<input
type="range"
class=""
@wheel="volumeWheel"
:step="cfg.audio.volumeStep"
min="0"
:max="cfg.audio.maxVolume"
v-model="mk.volume"
v-if="typeof mk.volume != 'undefined'"
@change="checkMuteChange()"
v-b-tooltip.hover
:title="formatVolumeTooltip()"
/>
</div>
</div>
</div>
</div>
<div
class="app-sidebar-notification backgroundNotification"
v-if="library.backgroundNotification.show"
>
<div class="message">
{{ library.backgroundNotification.message }} ({{
library.backgroundNotification.progress
}}
/ {{ library.backgroundNotification.total }})
</div>
</div>
</template>
</div>

View file

@ -0,0 +1,51 @@
<script type="text/x-template" id="app-content-area">
<div id="app-content" :scrollpos="$root.chrome.contentScrollPosY" scrollaxis="y"
:style="{'overflow': ($root.chrome.contentAreaScrolling ? '' : 'hidden')}">
<div id="navigation-bar" v-if="$root.getThemeDirective('appNavigation') == 'seperate'">
<button class="nav-item" @click="$root.navigateBack()">
<%- include('../svg/chevron-left.svg') %>
</button>
<button class="nav-item" @click="$root.navigateForward()">
<%- include('../svg/chevron-right.svg') %>
</button>
</div>
<!-- Include App Routes -->
<% for(var i = 0; i < Object.keys(env.appRoutes).length ; i++) { %>
<transition
<% if(env.appRoutes[i].onEnter) {
%>
v-on:enter="<%- env.appRoutes[i].onEnter %>"
<%
}
%>
:name="$root.chrome.desiredPageTransition">
<template
v-if="<%- env.appRoutes[i].condition %>">
<%- env.appRoutes[i].component %>
</template>
</transition>
<% } %>
<!-- Library - Made For You -->
<transition :name="$root.chrome.desiredPageTransition" v-on:enter="$root.getMadeForYou()">
<template v-if="$root.page == 'library-madeforyou'">
<%- include('../pages/madeforyou') %>');
%>
</template>
</transition>
<!-- Library - Artists-->
</div>
</script>
<script>
Vue.component('app-content-area', {
template: '#app-content-area',
data: function () {
return {
scrollPos: 0
}
},
methods: {
}
});
</script>

View file

@ -1,10 +1,10 @@
<script type="text/x-template" id="audio-controls"> <script type="text/x-template" id="audio-controls">
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.resetState()" <div class="modal-fullscreen addtoplaylist-panel" @click.self="app.modals.audioControls = false"
@contextmenu.self="app.resetState()"> @contextmenu.self="app.modals.audioControls = false">
<div class="modal-window"> <div class="modal-window">
<div class="modal-header"> <div class="modal-header">
<div class="modal-title">{{app.getLz('term.audioControls')}}</div> <div class="modal-title">{{app.getLz('term.audioControls')}}</div>
<button class="close-btn" @click="app.resetState()" :aria-label="app.getLz('action.close')"></button> <button class="close-btn" @click="app.modals.audioControls = false" :aria-label="app.getLz('action.close')"></button>
</div> </div>
<div class="modal-content"> <div class="modal-content">
<div class="md-option-line"> <div class="md-option-line">

View file

@ -1,10 +1,10 @@
<script type="text/x-template" id="audio-playbackrate"> <script type="text/x-template" id="audio-playbackrate">
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.resetState()" <div class="modal-fullscreen addtoplaylist-panel" @click.self="app.modals.audioPlaybackRate = false"
@contextmenu.self="app.resetState()"> @contextmenu.self="app.modals.audioPlaybackRate = false">
<div class="modal-window"> <div class="modal-window">
<div class="modal-header"> <div class="modal-header">
<div class="modal-title">{{app.getLz('settings.option.audio.changePlaybackRate')}}</div> <div class="modal-title">{{app.getLz('settings.option.audio.changePlaybackRate')}}</div>
<button class="close-btn" @click="app.resetState()" :aria-label="app.getLz('action.close')"></button> <button class="close-btn" @click="app.modals.audioPlaybackRate = false" :aria-label="app.getLz('action.close')"></button>
</div> </div>
<div class="modal-content"> <div class="modal-content">
<div class="md-option-line"> <div class="md-option-line">

View file

@ -1,10 +1,10 @@
<script type="text/x-template" id="audio-settings"> <script type="text/x-template" id="audio-settings">
<template> <template>
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.resetState()" @contextmenu.self="app.resetState()"> <div class="modal-fullscreen addtoplaylist-panel" @click.self="app.modals.audioSettings = false" @contextmenu.self="app.modals.audioSettings = false">
<div class="modal-window"> <div class="modal-window">
<div class="modal-header"> <div class="modal-header">
<div class="modal-title">{{app.getLz('term.audioSettings')}}</div> <div class="modal-title">{{app.getLz('term.audioSettings')}}</div>
<button class="close-btn" @click="app.resetState()" :aria-label="app.getLz('action.close')"></button> <button class="close-btn" @click="app.modals.audioSettings = false" :aria-label="app.getLz('action.close')"></button>
</div> </div>
<div class="modal-content"> <div class="modal-content">
<button class="playlist-item" <button class="playlist-item"
@ -23,7 +23,7 @@
<div class="name">{{app.getLz('settings.option.audio.changePlaybackRate')}}</div> <div class="name">{{app.getLz('settings.option.audio.changePlaybackRate')}}</div>
</button> </button>
<button class="playlist-item" <button class="playlist-item"
@click="$root.appRoute('audiolabs')" style="width:100%;"> @click="$root.openSettingsPage('audiolabs')" style="width:100%;">
<div class="icon"><%- include("../svg/speaker.svg") %></div> <div class="icon"><%- include("../svg/speaker.svg") %></div>
<div class="name">{{app.getLz('settings.option.audio.audioLab')}}</div> <div class="name">{{app.getLz('settings.option.audio.audioLab')}}</div>
</button> </button>

View file

@ -280,7 +280,7 @@
}, },
deletePreset() { deletePreset() {
let presets = this.$root.cfg.audio.equalizer.presets let presets = this.$root.cfg.audio.equalizer.presets
bootbox.confirm(app.getLz('term.deletepreset.warn'), (result) => { app.confirm(app.getLz('term.deletepreset.warn'), (result) => {
if (result) { if (result) {
this.changePreset("default") this.changePreset("default")
// find the preset by id (preset) and remove it // find the preset by id (preset) and remove it
@ -291,7 +291,7 @@
}) })
}, },
close() { close() {
app.resetState() app.modals.equalizer = false
}, },
changeVibrantBass() { changeVibrantBass() {
if (app.cfg.audio.equalizer.vibrantBass !== '0') { if (app.cfg.audio.equalizer.vibrantBass !== '0') {
@ -351,7 +351,7 @@
}, },
addPreset() { addPreset() {
let self = this let self = this
bootbox.prompt(app.getLz('term.newpreset.name'), (res) => { app.prompt(app.getLz('term.newpreset.name'), (res) => {
if (res) { if (res) {
let eqSettings = Clone(app.cfg.audio.equalizer) let eqSettings = Clone(app.cfg.audio.equalizer)
let newPreset = new self.eqPreset() let newPreset = new self.eqPreset()
@ -386,7 +386,7 @@
}, },
importPreset() { importPreset() {
let self = this let self = this
bootbox.prompt("Enter preset share code", (res) => { app.prompt("Enter preset share code", (res) => {
if (res) { if (res) {
let preset = JSON.parse(atob(res)) let preset = JSON.parse(atob(res))
if (preset.frequencies && preset.gain && preset.Q && preset.mix && preset.vibrantBass) { if (preset.frequencies && preset.gain && preset.Q && preset.mix && preset.vibrantBass) {

View file

@ -3,13 +3,34 @@
<div class="background"> <div class="background">
<div class="bgArtworkMaterial"> <div class="bgArtworkMaterial">
<div class="bg-artwork-container"> <div class="bg-artwork-container">
<img v-if="(app.cfg.visual.bg_artwork_rotation && app.animateBackground)" class="bg-artwork a" :src="(image ?? '').replace('{w}','30').replace('{h}','30')"> <img v-if="(app.cfg.visual.bg_artwork_rotation && app.animateBackground)" class="bg-artwork a"
<img v-if="(app.cfg.visual.bg_artwork_rotation && app.animateBackground)" class="bg-artwork b" :src="(image ?? '').replace('{w}','30').replace('{h}','30')"> :src="(image ?? '').replace('{w}','30').replace('{h}','30')">
<img v-if="!(app.cfg.visual.bg_artwork_rotation && app.animateBackground)" class="bg-artwork no-animation" :src="(image ?? '').replace('{w}','30').replace('{h}','30')"> <img v-if="(app.cfg.visual.bg_artwork_rotation && app.animateBackground)" class="bg-artwork b"
:src="(image ?? '').replace('{w}','30').replace('{h}','30')">
<img v-if="!(app.cfg.visual.bg_artwork_rotation && app.animateBackground)"
class="bg-artwork no-animation" :src="(image ?? '').replace('{w}','30').replace('{h}','30')">
</div> </div>
</div> </div>
</div> </div>
<div class="row fs-row"> <div class="fs-header" v-if="immersiveEnabled">
<div class="top-nav-group">
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('home.title')" svg-icon="./assets/feather/home.svg" svg-icon-name="home" page="home">
</sidebar-library-item>
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('term.listenNow')" svg-icon="./assets/feather/play-circle.svg" svg-icon-name="listenNow"
page="listen_now"></sidebar-library-item>
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('term.browse')" svg-icon="./assets/feather/globe.svg" svg-icon-name="browse" page="browse">
</sidebar-library-item>
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('term.radio')" svg-icon="./assets/feather/radio.svg" svg-icon-name="radio" page="radio">
</sidebar-library-item>
<sidebar-library-item @click.native="tabMode = 'catalog'" :name="$root.getLz('term.library')" svg-icon="./assets/feather/radio.svg" svg-icon-name="library" page="library">
</sidebar-library-item>
<sidebar-library-item @click.native="tabMode = ''" :name="$root.getLz('term.nowPlaying')" svg-icon="./assets/play.svg" svg-icon-name="nowPlaying" page="nowPlaying">
</sidebar-library-item>
<sidebar-library-item @click.native="tabMode = 'catalog'" name="" svg-icon="./assets/search.svg" svg-icon-name="search" page="search">
</sidebar-library-item>
</div>
</div>
<div class="row fs-row" v-if="tabMode != 'catalog'">
<div class="col artwork-col"> <div class="col artwork-col">
<div class="artwork" @click="app.fullscreen(false)"> <div class="artwork" @click="app.fullscreen(false)">
<mediaitem-artwork <mediaitem-artwork
@ -55,45 +76,63 @@
</div> </div>
<div class="control-buttons"> <div class="control-buttons">
<div class="app-chrome-item display--large"> <div class="app-chrome-item display--large">
<button class="playback-button--small shuffle" v-if="$root.mk.shuffleMode == 0" :class="$root.isDisabled() && 'disabled'" <button class="playback-button--small shuffle" v-if="$root.mk.shuffleMode == 0"
@click="$root.mk.shuffleMode = 1" :title="$root.getLz('term.enableShuffle')" v-b-tooltip.hover></button> :class="$root.isDisabled() && 'disabled'"
<button class="playback-button--small shuffle active" v-else :class="$root.isDisabled() && 'disabled'" @click="$root.mk.shuffleMode = 1" :title="$root.getLz('term.enableShuffle')"
@click="$root.mk.shuffleMode = 0" :title="$root.getLz('term.disableShuffle')" v-b-tooltip.hover></button> v-b-tooltip.hover></button>
<button class="playback-button--small shuffle active" v-else
:class="$root.isDisabled() && 'disabled'"
@click="$root.mk.shuffleMode = 0"
:title="$root.getLz('term.disableShuffle')" v-b-tooltip.hover></button>
</div> </div>
<div class="app-chrome-item display--large"> <div class="app-chrome-item display--large">
<button class="playback-button previous" @click="$root.prevButton()" :class="$root.isPrevDisabled() && 'disabled'" <button class="playback-button previous" @click="$root.prevButton()"
:class="$root.isPrevDisabled() && 'disabled'"
: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 display--large"> <div class="app-chrome-item display--large">
<button class="playback-button stop" @click="$root.mk.stop()" <button class="playback-button stop" @click="$root.mk.stop()"
v-if="$root.mk.isPlaying && $root.mk.nowPlayingItem.attributes.playParams.kind == 'radioStation'" v-if="$root.mk.isPlaying && $root.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="$root.mk.pause()" v-else-if="$root.mk.isPlaying" <button class="playback-button pause" @click="$root.mk.pause()"
v-else-if="$root.mk.isPlaying"
:title="$root.getLz('term.pause')" v-b-tooltip.hover></button> :title="$root.getLz('term.pause')" v-b-tooltip.hover></button>
<button class="playback-button play" @click="$root.mk.play()" v-else :title="$root.getLz('term.play')" <button class="playback-button play" @click="$root.mk.play()" v-else
:title="$root.getLz('term.play')"
v-b-tooltip.hover></button> v-b-tooltip.hover></button>
</div> </div>
<div class="app-chrome-item display--large"> <div class="app-chrome-item display--large">
<button class="playback-button next" @click="$root.skipToNextItem()" :class="$root.isNextDisabled() && 'disabled'" <button class="playback-button next" @click="$root.skipToNextItem()"
:class="$root.isNextDisabled() && 'disabled'"
: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 display--large"> <div class="app-chrome-item display--large">
<button class="playback-button--small repeat" v-if="$root.mk.repeatMode == 0" :class="$root.isDisabled() && 'disabled'" <button class="playback-button--small repeat" v-if="$root.mk.repeatMode == 0"
@click="$root.mk.repeatMode = 1" :title="$root.getLz('term.enableRepeatOne')" v-b-tooltip.hover></button> :class="$root.isDisabled() && 'disabled'"
@click="$root.mk.repeatMode = 1"
:title="$root.getLz('term.enableRepeatOne')" v-b-tooltip.hover></button>
<button class="playback-button--small repeat repeatOne" @click="mk.repeatMode = 2" <button class="playback-button--small repeat repeatOne" @click="mk.repeatMode = 2"
:class="$root.isDisabled() && 'disabled'" v-else-if="$root.mk.repeatMode == 1" :class="$root.isDisabled() && 'disabled'"
v-else-if="$root.mk.repeatMode == 1"
:title="$root.getLz('term.disableRepeatOne')" v-b-tooltip.hover></button> :title="$root.getLz('term.disableRepeatOne')" v-b-tooltip.hover></button>
<button class="playback-button--small repeat active" @click="$root.mk.repeatMode = 0" <button class="playback-button--small repeat active"
:class="$root.isDisabled() && 'disabled'" v-else-if="$root.mk.repeatMode == 2" :title="$root.getLz('term.disableRepeat')" @click="$root.mk.repeatMode = 0"
:class="$root.isDisabled() && 'disabled'"
v-else-if="$root.mk.repeatMode == 2"
:title="$root.getLz('term.disableRepeat')"
v-b-tooltip.hover></button> v-b-tooltip.hover></button>
</div> </div>
</div> </div>
</div> </div>
<div class="app-chrome-item volume display--large"> <div class="app-chrome-item volume display--large">
<div class="input-container"> <div class="input-container">
<button class="volume-button--small volume" @click="app.muteButtonPressed()" :class="{'active': app.cfg.audio.volume == 0}" <button class="volume-button--small volume" @click="app.muteButtonPressed()"
:title="app.cfg.audio.muted ? $root.getLz('term.unmute') : $root.getLz('term.mute')" v-b-tooltip.hover></button> :class="{'active': app.cfg.audio.volume == 0}"
<input type="range" class="slider" @wheel="app.volumeWheel" :step="app.cfg.audio.volumeStep" min="0" :max="app.cfg.audio.maxVolume" v-model="app.mk.volume" :title="app.cfg.audio.muted ? $root.getLz('term.unmute') : $root.getLz('term.mute')"
v-b-tooltip.hover></button>
<input type="range" class="slider" @wheel="app.volumeWheel"
:step="app.cfg.audio.volumeStep" min="0" :max="app.cfg.audio.maxVolume"
v-model="app.mk.volume"
v-if="typeof app.mk.volume != 'undefined'" @change="app.checkMuteChange()" v-if="typeof app.mk.volume != 'undefined'" @change="app.checkMuteChange()"
v-b-tooltip.hover :title="$root.formatVolumeTooltip()"> v-b-tooltip.hover :title="$root.formatVolumeTooltip()">
</div> </div>
@ -101,7 +140,6 @@
</div> </div>
</template> </template>
</div> </div>
</div>
<div class="col right-col" v-if="tabMode != ''"> <div class="col right-col" v-if="tabMode != ''">
<!-- <div class="fs-info"> <!-- <div class="fs-info">
<div>Name</div> <div>Name</div>
@ -113,18 +151,25 @@
:richlyrics="richlyrics"></lyrics-view> :richlyrics="richlyrics"></lyrics-view>
</div> </div>
<div class="queue-col" v-if="tabMode == 'queue'"> <div class="queue-col" v-if="tabMode == 'queue'">
<cider-queue v-if="tabMode == 'queue'" ref="queue" ></cider-queue> <cider-queue v-if="tabMode == 'queue'" ref="queue"></cider-queue>
</div> </div>
</div> </div>
</div> </div>
<div class="app-content-container" v-else>
<app-content-area></app-content-area>
</div>
<div class="tab-toggles"> <div class="tab-toggles">
<div class="lyrics" :class="{active: tabMode == 'lyrics'}" @click="tabMode = (tabMode == 'lyrics') ? '' : 'lyrics'"></div> <div class="lyrics" :class="{active: tabMode == 'lyrics'}"
<div class="queue" :class="{active: tabMode == 'queue'}" @click="tabMode = (tabMode == 'queue') ? '' :'queue'"></div> @click="tabMode = (tabMode == 'lyrics') ? '' : 'lyrics'"></div>
<div class="queue" :class="{active: tabMode == 'queue'}"
@click="tabMode = (tabMode == 'queue') ? '' :'queue'"></div>
<div class="queue" :class="{active: tabMode == 'catalog'}"
v-if="false"
@click="tabMode = (tabMode == 'catalog') ? '' :'catalog'"></div>
</div> </div>
</div> </div>
</script> </script>
<script> <script>
@ -152,7 +197,8 @@
return { return {
app: this.$root, app: this.$root,
tabMode: "lyrics", tabMode: "lyrics",
video: null video: null,
immersiveEnabled: app.cfg.advanced.experiments.includes("immersive-preview")
} }
}, },
async mounted() { async mounted() {
@ -173,7 +219,7 @@
} else if (app.mk.nowPlayingItem._container.type == "library-albums") { } else if (app.mk.nowPlayingItem._container.type == "library-albums") {
try { try {
const result = (await app.mk.api.v3.music(`/v1/me/library/albums/${app.mk.nowPlayingItem._container.id}/catalog` const result = (await app.mk.api.v3.music(`/v1/me/library/albums/${app.mk.nowPlayingItem._container.id}/catalog`
, { "fields": "editorialArtwork,editorialVideo" })).data.data[0].attributes?.editorialVideo?.motionDetailSquare?.video , {"fields": "editorialArtwork,editorialVideo"})).data.data[0].attributes?.editorialVideo?.motionDetailSquare?.video
if (result) { if (result) {
this.video = result this.video = result
} else { } else {

View file

@ -1,6 +1,7 @@
<script type="text/x-template" id="mediaitem-artwork"> <script type="text/x-template" id="mediaitem-artwork">
<div class="mediaitem-artwork" :style="awStyle" @contextmenu="contextMenu" :class="[{'rounded': (type == 'artists')}, classes]" :key="url"> <div class="mediaitem-artwork" :style="awStyle" @contextmenu="contextMenu" :class="[{'rounded': (type == 'artists')}, classes]" :key="url">
<img :src="app.getMediaItemArtwork(url, size, width)" <img :src="imgSrc"
ref="image"
decoding="async" decoding="async"
loading="lazy" loading="lazy"
:style="imgStyle" :style="imgStyle"
@ -47,6 +48,10 @@
shadow: { shadow: {
type: String, type: String,
default: '' default: ''
},
upscaling: {
type: Boolean,
default: false
} }
}, },
data: function () { data: function () {
@ -63,15 +68,37 @@
opacity: 0, opacity: 0,
transition: "opacity .25s linear" transition: "opacity .25s linear"
}, },
classes: [] classes: [],
imgSrc: ""
}
},
computed: {
windowRelativeScale: function () {
return app.$store.state.windowRelativeScale;
}
},
watch: {
windowRelativeScale: function (newValue, oldValue) {
this.swapImage(newValue)
},
url: function (newValue, oldValue) {
this.imgSrc = app.getMediaItemArtwork(this.url, this.size, this.width)
} }
}, },
mounted() { mounted() {
this.getClasses() this.getClasses()
this.imgSrc = app.getMediaItemArtwork(this.url, this.size, this.width)
}, },
methods: { methods: {
swapImage(newValue) {
if(!this.upscaling || window.devicePixelRatio !== 1) return
if (newValue > 1.5) {
this.imgSrc = app.getMediaItemArtwork(this.url, parseInt(this.size * 2.0), parseInt(this.size * 2.0));
}
},
imgLoaded() { imgLoaded() {
this.imgStyle.opacity = 1 this.imgStyle.opacity = 1
this.swapImage(app.$store.state.windowRelativeScale)
// this.awStyle.background = "" // this.awStyle.background = ""
}, },
contextMenu(event) { contextMenu(event) {

View file

@ -1,5 +1,5 @@
<script type="text/x-template" id="mediaitem-list-item"> <script type="text/x-template" id="mediaitem-list-item">
<div v-observe-visibility="{callback: visibilityChanged}" <div v-observe-visibility="{callback: visibilityChanged, throttle: 100}"
@contextmenu="contextMenu" @contextmenu="contextMenu"
@click="select" @click="select"
:data-id="itemId" :data-id="itemId"
@ -67,7 +67,7 @@
</template> </template>
</div> </div>
</div> </div>
<div class="heart-icon" v-if="!(app.mk.isPlaying && (((app.mk.nowPlayingItem._songId ?? (app.mk.nowPlayingItem.songId ?? app.mk.nowPlayingItem.id )) == itemId) || (app.mk.nowPlayingItem.id == item.id)))"> <div class="heart-icon">
<!-- <div class="heart-unfilled" v-if="isLoved == false" :style="{'--url': 'url(./assets/feather/heart.svg)'}" /> --> <!-- <div class="heart-unfilled" v-if="isLoved == false" :style="{'--url': 'url(./assets/feather/heart.svg)'}" /> -->
<div class="heart-filled" v-if="isLoved == true" :style="{'--url': 'url(./assets/feather/heart-fill.svg)'}" /> <div class="heart-filled" v-if="isLoved == true" :style="{'--url': 'url(./assets/feather/heart-fill.svg)'}" />
</div> </div>
@ -123,8 +123,8 @@
}, },
mounted() { mounted() {
if (this.item.attributes.playParams) { if (this.item.attributes.playParams) {
this.itemId = this.item.attributes.playParams.id ?? this.item.id; this.itemId = this.item.attributes.playParams?.id ?? this.item.id;
this.isLibrary = this.item.attributes.playParams.isLibrary ?? false; this.isLibrary = this.item.attributes.playParams?.isLibrary ?? false;
} else { } else {
this.itemId = this.item.id; this.itemId = this.item.id;
} }
@ -143,6 +143,9 @@
return color return color
}, },
async checkLibrary() { async checkLibrary() {
if ((this.item?.id ?? '').toString().startsWith('ciderlocal')){
return true
}
if (this.addedToLibrary) { return this.addedToLibrary } if (this.addedToLibrary) { return this.addedToLibrary }
if (this.item.type.includes("library-playlists") || this.item.type.includes("station")) { if (this.item.type.includes("library-playlists") || this.item.type.includes("station")) {
this.addedToLibrary = true this.addedToLibrary = true
@ -155,7 +158,7 @@
}, },
getClasses() { getClasses() {
this.addClasses = {} this.addClasses = {}
if (typeof this.item.attributes.playParams == "undefined") { if (typeof this.item.attributes.playParams == "undefined" && this.item.type != "podcast-episodes") {
this.addClasses["disabled"] = true this.addClasses["disabled"] = true
} }
if (this.classList) { if (this.classList) {
@ -167,7 +170,7 @@
}, },
dragStart(evt) { dragStart(evt) {
evt.dataTransfer.setData('text/plain', JSON.stringify({ evt.dataTransfer.setData('text/plain', JSON.stringify({
type: this.item.attributes.playParams.kind ?? this.item.type, type: this.item.attributes.playParams?.kind ?? this.item.type,
id: this.item.id id: this.item.id
})) }))
}, },
@ -182,20 +185,23 @@
return minutes + ":" + (seconds < 10 ? '0' : '') + seconds; return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
}, },
getDataType() { getDataType() {
let type = ''
if (typeof this.item.attributes.playParams != "undefined") { if (typeof this.item.attributes.playParams != "undefined") {
if (this.item.attributes.playParams.isLibrary) { if (this.item.attributes.playParams?.isLibrary) {
return this.item.type type = this.item.type
} else { } else {
return this.item.attributes.playParams.kind type = this.item.attributes.playParams?.kind
} }
} else { } else {
return this.item.type type = this.item.type
} }
if (type == 'podcast-episodes') type = 'episode';
return type;
}, },
select(e) { select(e) {
let data_type = this.getDataType() let data_type = this.getDataType()
let item_id = this.item.attributes.playParams.id ?? this.item.id let item_id = this.item.attributes.playParams?.id ?? this.item.id
let isLibrary = this.item.attributes.playParams.isLibrary ?? false let isLibrary = this.item.attributes.playParams?.isLibrary ?? false
if (e.shiftKey) { if (e.shiftKey) {
if (this.index != -1) { if (this.index != -1) {
@ -259,8 +265,8 @@
async contextMenu(event) { async contextMenu(event) {
let self = this let self = this
let data_type = this.getDataType() let data_type = this.getDataType()
let item_id = this.item.attributes.playParams.id ?? this.item.id let item_id = this.item.attributes.playParams?.id ?? this.item.id
let isLibrary = this.item.attributes.playParams.isLibrary ?? false let isLibrary = this.item.attributes.playParams?.isLibrary ?? false
let useMenu = "normal" let useMenu = "normal"
if (app.selectedMediaItems.length <= 1) { if (app.selectedMediaItems.length <= 1) {
@ -319,6 +325,7 @@
app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] }) app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] })
} }
} }
console.log(itemsToPlay)
app.selectedMediaItems = [] app.selectedMediaItems = []
} }
}, },
@ -400,7 +407,9 @@
"name": app.getLz('action.playNext'), "name": app.getLz('action.playNext'),
"icon": "./assets/arrow-bend-up.svg", "icon": "./assets/arrow-bend-up.svg",
"action": function () { "action": function () {
app.mk.playNext({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id }) let type = self.item.attributes.playParams?.kind ?? self.item.type
if (type == "podcast-episodes") {type = "episode"}
app.mk.playNext({ [type]: self.item.attributes.playParams?.id ?? self.item.id })
app.mk.queue._reindex() app.mk.queue._reindex()
app.selectedMediaItems = [] app.selectedMediaItems = []
} }
@ -409,7 +418,9 @@
"name": app.getLz('action.playLater'), "name": app.getLz('action.playLater'),
"icon": "./assets/arrow-bend-down.svg", "icon": "./assets/arrow-bend-down.svg",
"action": function () { "action": function () {
app.mk.playLater({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id }) let type = self.item.attributes.playParams?.kind ?? self.item.type
if (type == "podcast-episodes") {type = "episode"}
app.mk.playLater({ [type]: self.item.attributes.playParams?.id ?? self.item.id })
app.mk.queue._reindex() app.mk.queue._reindex()
app.selectedMediaItems = [] app.selectedMediaItems = []
} }
@ -418,7 +429,7 @@
"icon": "./assets/feather/radio.svg", "icon": "./assets/feather/radio.svg",
"name": app.getLz('action.startRadio'), "name": app.getLz('action.startRadio'),
"action": function () { "action": function () {
app.mk.setStationQueue({ song: self.item.attributes.playParams.id ?? self.item.id }).then(() => { app.mk.setStationQueue({ song: self.item.attributes.playParams?.id ?? self.item.id }).then(() => {
app.mk.play() app.mk.play()
app.selectedMediaItems = [] app.selectedMediaItems = []
}) })
@ -444,7 +455,7 @@
"action": function () { "action": function () {
if (!self.item.attributes.url && self.item.relationships) { if (!self.item.attributes.url && self.item.relationships) {
if (self.item.relationships.catalog) { if (self.item.relationships.catalog) {
app.mkapi(self.item.attributes.playParams.kind, false, self.item.relationships.catalog.data[0].id).then(u => { self.app.copyToClipboard((u.data.data.length && u.data.data.length > 0) ? u.data.data[0].attributes.url : u.data.data.attributes.url) }) app.mkapi(self.item.attributes.playParams?.kind, false, self.item.relationships.catalog.data[0].id).then(u => { self.app.copyToClipboard((u.data.data.length && u.data.data.length > 0) ? u.data.data[0].attributes.url : u.data.data.attributes.url) })
} }
} else { } else {
self.app.copyToClipboard(self.item.attributes.url) self.app.copyToClipboard(self.item.attributes.url)
@ -457,7 +468,7 @@
"action": function () { "action": function () {
if (!self.item.attributes.url && self.item.relationships) { if (!self.item.attributes.url && self.item.relationships) {
if (self.item.relationships.catalog) { if (self.item.relationships.catalog) {
app.mkapi(self.item.attributes.playParams.kind, false, self.item.relationships.catalog.data[0].id).then(u => { self.app.songLinkShare((u.data.data.length && u.data.data.length > 0) ? u.data.data[0].attributes.url : u.data.data.attributes.url) }) app.mkapi(self.item.attributes.playParams?.kind, false, self.item.relationships.catalog.data[0].id).then(u => { self.app.songLinkShare((u.data.data.length && u.data.data.length > 0) ? u.data.data[0].attributes.url : u.data.data.attributes.url) })
} }
} else { } else {
self.app.songLinkShare(self.item.attributes.url) self.app.songLinkShare(self.item.attributes.url)
@ -526,9 +537,9 @@
}, },
addToLibrary() { addToLibrary() {
let item = this.item let item = this.item
if (item.attributes.playParams.id) { if (item.attributes.playParams?.id) {
console.log('adding to library', item.attributes.playParams.id) console.log('adding to library', item.attributes.playParams?.id)
app.addToLibrary(item.attributes.playParams.id.toString()) app.addToLibrary(item.attributes.playParams?.id.toString())
this.addedToLibrary = true this.addedToLibrary = true
} else if (item.id) { } else if (item.id) {
console.log('adding to library', item.id) console.log('adding to library', item.id)
@ -539,14 +550,14 @@
async removeFromLibrary() { async removeFromLibrary() {
let item = this.item let item = this.item
let params = { "fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library" } let params = { "fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library" }
let id = item.id ?? item.attributes.playParams.id let id = item.id ?? item.attributes.playParams?.id
let res = await app.mkapi(item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.playParams.id ?? item.id, params); let res = await app.mkapi(item.attributes.playParams?.kind ?? item.type, item.attributes.playParams?.isLibrary ?? false, item.attributes.playParams?.id ?? item.id, params);
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) { if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
id = res.relationships.library.data[0].id id = res.relationships.library.data[0].id
} }
let kind = this.item.attributes.playParams.kind ?? this.data.item ?? ''; let kind = this.item.attributes.playParams?.kind ?? this.data.item ?? '';
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
if (item.attributes.playParams.id) { if (item.attributes.playParams?.id) {
console.log('remove from library', id) console.log('remove from library', id)
app.removeFromLibrary(truekind, id) app.removeFromLibrary(truekind, id)
this.addedToLibrary = false this.addedToLibrary = false
@ -560,9 +571,9 @@
let item = this.item let item = this.item
let parent = this.parent let parent = this.parent
let childIndex = this.index let childIndex = this.index
let kind = (item.attributes.playParams ? (item.attributes.playParams.kind ?? (item.type ?? '')) : (item.type ?? '')); let kind = (item.attributes.playParams ? (item.attributes.playParams?.kind ?? (item.type ?? '')) : (item.type ?? ''));
let id = (item.attributes.playParams ? (item.attributes.playParams.id ?? (item.id ?? '')) : (item.id ?? ''));; let id = (item.attributes.playParams ? (item.attributes.playParams?.id ?? (item.id ?? '')) : (item.id ?? ''));;
let isLibrary = item.attributes.playParams ? (item.attributes.playParams.isLibrary ?? false) : false; let isLibrary = item.attributes.playParams ? (item.attributes.playParams?.isLibrary ?? false) : false;
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind; let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
console.log(item, parent, childIndex, kind, id, isLibrary, kind == "playlists", id.startsWith("p.") || id.startsWith("pl.u")) console.log(item, parent, childIndex, kind, id, isLibrary, kind == "playlists", id.startsWith("p.") || id.startsWith("pl.u"))
app.mk.stop().then(() => { app.mk.stop().then(() => {
@ -578,7 +589,7 @@
array[j] = temp; array[j] = temp;
} }
} }
app.mk.setQueue({ [truekind]: [item.attributes.playParams.id ?? item.id], parameters: { l: this.app.mklang } }).then(function () { app.mk.setQueue({ [truekind]: [item.attributes.playParams?.id ?? item.id], parameters: { l: this.app.mklang } }).then(function () {
app.mk.play().then(function () { app.mk.play().then(function () {
var playlistId = id var playlistId = id
function getPlaylist(id, isLibrary) { function getPlaylist(id, isLibrary) {
@ -624,12 +635,12 @@
} }
else { else {
app.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url) app.playMediaItemById(item.attributes.playParams?.id ?? item.id, item.attributes.playParams?.kind ?? item.type, item.attributes.playParams?.isLibrary ?? false, item.attributes.url)
} }
}) })
}, },
route() { route() {
let kind = (this.item.attributes.playParams ? (this.item.attributes.playParams.kind ?? (this.item.type ?? '')) : (this.item.type ?? '')); let kind = (this.item.attributes.playParams ? (this.item.attributes.playParams?.kind ?? (this.item.type ?? '')) : (this.item.type ?? ''));
if (kind.toLowerCase().includes('album') || kind.toLowerCase().includes('playlist')) { if (kind.toLowerCase().includes('album') || kind.toLowerCase().includes('playlist')) {
app.routeView(this.item) app.routeView(this.item)
} else { } else {

View file

@ -17,6 +17,7 @@
:url="getArtworkUrl()" :url="getArtworkUrl()"
:video="(item.attributes != null && item.attributes.editorialVideo != null) ? (item.attributes.editorialVideo.motionDetailSquare ? item.attributes.editorialVideo.motionDetailSquare.video : (item.attributes.editorialVideo.motionSquareVideo1x1 ? item.attributes.editorialVideo.motionSquareVideo1x1.video : '')) : '' " :video="(item.attributes != null && item.attributes.editorialVideo != null) ? (item.attributes.editorialVideo.motionDetailSquare ? item.attributes.editorialVideo.motionDetailSquare.video : (item.attributes.editorialVideo.motionSquareVideo1x1 ? item.attributes.editorialVideo.motionSquareVideo1x1.video : '')) : '' "
:size="size" :size="size"
:upscaling="true"
shadow="subtle" shadow="subtle"
:bgcolor="getBgColor()" :bgcolor="getBgColor()"
:video-priority="forceVideo" :video-priority="forceVideo"
@ -542,14 +543,14 @@
let followAction = "follow" let followAction = "follow"
let followActions = { let followActions = {
follow: { follow: {
icon: "./assets/feather/plus-circle.svg", icon: "./assets/star.svg",
name: app.getLz('action.favorite'), name: app.getLz('action.favorite'),
action: () => { action: () => {
self.$root.setArtistFavorite(this.item.id, true) self.$root.setArtistFavorite(this.item.id, true)
} }
}, },
unfollow: { unfollow: {
icon: "./assets/feather/x-circle.svg", icon: "./assets/star.svg",
name: app.getLz('action.removeFavorite'), name: app.getLz('action.removeFavorite'),
action: () => { action: () => {
self.$root.setArtistFavorite(this.item.id, false) self.$root.setArtistFavorite(this.item.id, false)

View file

@ -0,0 +1,65 @@
<script type="text/x-template" id="pathmenu">
<div class="spatialproperties-panel castmenu pathmenu modal-fullscreen" @click.self="close()" @contextmenu.self="close()">
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">{{'Edit Paths'}}</div>
<button class="close-btn" @click="close()" :aria-label="$root.getLz('action.close')"></button>
</div>
<div class="modal-content">
<template v-for="folder of folders">
<div class="md-option-line">
<div class="md-option-segment">
{{folder}}
</div>
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" @click="remove(folder)">
{{'Remove'}}
</button>
</div>
</div>
</template>
<div class="md-option-line">
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" @click="add()">
{{'Add Path'}}
</button>
</div>
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('pathmenu', {
template: '#pathmenu',
data: function () {
return {
folders: [],
}
},
mounted() {
this.folders = this.$root.cfg.libraryPrefs.localPaths;
},
watch:{},
methods: {
close() {
this.$root.modals.pathMenu = false
},
async add(){
const result = await ipcRenderer.invoke('folderSelector')
for (i of result){
if (this.folders.findIndex(x => x.startsWith(i)) == -1){
this.folders.push(i)
}
}
this.$root.cfg.libraryPrefs.localPaths = this.folders;
ipcRenderer.invoke("scanLibrary")
},
remove(dir){
this.folders = this.folders.filter(item => item !== dir)
this.$root.cfg.libraryPrefs.localPaths = this.folders;
ipcRenderer.invoke("scanLibrary")
}
}
});
</script>

View file

@ -1,5 +1,5 @@
<script type="text/x-template" id="keybinds-settings"> <script type="text/x-template" id="keybinds-settings">
<div class="content-inner keybinds-page"> <div class="keybinds-page">
<div class="md-option-header"> <div class="md-option-header">
<span>{{$root.getLz('settings.option.general.keybindings')}}</span> <span>{{$root.getLz('settings.option.general.keybindings')}}</span>
</div> </div>
@ -265,7 +265,7 @@
document.body.removeChild(blur); document.body.removeChild(blur);
clearTimeout(keyBindTimeout); clearTimeout(keyBindTimeout);
notyf.success(app.getLz('settings.notyf.general.keybindings.update.success')); notyf.success(app.getLz('settings.notyf.general.keybindings.update.success'));
bootbox.confirm(app.getLz("settings.prompt.general.keybindings.update.success"), (ok) => { app.confirm(app.getLz("settings.prompt.general.keybindings.update.success"), (ok) => {
if (ok) ipcRenderer.invoke("relaunchApp") if (ok) ipcRenderer.invoke("relaunchApp")
}) })
} else { } else {
@ -296,7 +296,7 @@
app.cfg.general.keybindings.zoomrst = [app.platform == "darwin" ? "Command" : "Control", "num0"]; app.cfg.general.keybindings.zoomrst = [app.platform == "darwin" ? "Command" : "Control", "num0"];
app.cfg.general.keybindings.openDeveloperTools = [app.platform == "darwin" ? "Command" : "Control", app.platform == "darwin" ? "Option" : "Shift", "I"]; app.cfg.general.keybindings.openDeveloperTools = [app.platform == "darwin" ? "Command" : "Control", app.platform == "darwin" ? "Option" : "Shift", "I"];
notyf.success(app.getLz('settings.notyf.general.keybindings.update.success')); notyf.success(app.getLz('settings.notyf.general.keybindings.update.success'));
bootbox.confirm(app.getLz("settings.prompt.general.keybindings.update.success"), (ok) => { app.confirm(app.getLz("settings.prompt.general.keybindings.update.success"), (ok) => {
if (ok) ipcRenderer.invoke("relaunchApp") if (ok) ipcRenderer.invoke("relaunchApp")
}) })
}, },

View file

@ -1,5 +1,5 @@
<script type="text/x-template" id="plugins-github"> <script type="text/x-template" id="plugins-github">
<div class="content-inner github-themes-page"> <div class="github-themes-page">
<div class="gh-header"> <div class="gh-header">
<div class="row"> <div class="row">
<div class="col nopadding"> <div class="col nopadding">
@ -16,7 +16,7 @@
<div class="repos-list"> <div class="repos-list">
<ul class="list-group list-group-flush"> <ul class="list-group list-group-flush">
<li @click="showRepo(repo)" class="list-group-item list-group-item-dark" <li @click="showRepo(repo)" class="list-group-item list-group-item-dark"
:style="{'background': (repo.id == openRepo.id) ? 'var(--keyColor)' : ''}" :style="{'background': (repo.id == openRepo.id) ? 'var(--keyColor)' : '', 'border-radius': '5px'}"
v-for="repo in repos"> v-for="repo in repos">
<div class="row"> <div class="row">
<div class="col flex-center"> <div class="col flex-center">
@ -125,13 +125,13 @@
let msg = app.stringTemplateParser(app.getLz('settings.option.visual.plugin.github.install.confirm'), { let msg = app.stringTemplateParser(app.getLz('settings.option.visual.plugin.github.install.confirm'), {
repo: repo.full_name repo: repo.full_name
}); });
bootbox.confirm(msg, (res) => { app.confirm(msg, (res) => {
if (res) { if (res) {
ipcRenderer.once("plugin-installed", (event, arg) => { ipcRenderer.once("plugin-installed", (event, arg) => {
if (arg.success) { if (arg.success) {
self.themes = [] self.themes = []
notyf.success(app.getLz('settings.notyf.visual.plugin.install.success')); notyf.success(app.getLz('settings.notyf.visual.plugin.install.success'));
bootbox.confirm(app.getLz("settings.prompt.visual.plugin.github.success"), (ok)=>{ app.confirm(app.getLz("settings.prompt.visual.plugin.github.success"), (ok)=>{
if (ok) { if (ok) {
ipcRenderer.invoke("relaunchApp") ipcRenderer.invoke("relaunchApp")
} else { } else {
@ -148,12 +148,12 @@
}, },
installThemeURL() { installThemeURL() {
let self = this let self = this
bootbox.prompt(app.getLz('settings.prompt.visual.plugin.github.URL'), (result) => { app.prompt(app.getLz('settings.prompt.visual.plugin.github.URL'), (result) => {
if (result) { if (result) {
ipcRenderer.once("plugin-installed", (event, arg) => { ipcRenderer.once("plugin-installed", (event, arg) => {
if (arg.success) { if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes") self.themes = ipcRenderer.sendSync("get-themes")
bootbox.confirm(app.getLz("settings.prompt.visual.plugin.github.success"), (ok)=>{ app.confirm(app.getLz("settings.prompt.visual.plugin.github.success"), (ok)=>{
if (ok) { if (ok) {
ipcRenderer.invoke("relaunchApp") ipcRenderer.invoke("relaunchApp")
} else { } else {

View file

@ -0,0 +1,197 @@
<script type="text/x-template" id="themes-github">
<div class="github-themes-page">
<div class="gh-header">
<div class="row">
<div class="col nopadding">
<h1 class="header-text">{{$root.getLz('settings.header.visual.theme.github.page')}}</h1>
</div>
<div class="col-auto nopadding flex-center">
<button class="md-btn md-btn-small md-btn-block" @click="$root.openSettingsPage('styles')">
{{$root.getLz('settings.option.visual.theme.manageStyles')}}
</button>
</div>
<div class="col-auto flex-center">
<button class="md-btn md-btn-small md-btn-block" @click="$root.checkForThemeUpdates()">
{{ $root.getLz('settings.option.visual.theme.checkForUpdates') }}
</button>
</div>
<div class="col-auto nopadding flex-center">
<button class="md-btn md-btn-small md-btn-block" @click="installThemeURL()">
{{$root.getLz('settings.option.visual.theme.github.download')}}
</button>
</div>
</div>
</div>
<div class="gh-content">
<div class="repos-list">
<ul class="list-group list-group-flush">
<li @click="showRepo(repo)" class="list-group-item list-group-item-dark"
:style="{'background': (repo.id == openRepo.id) ? 'var(--keyColor)' : ''}"
v-for="repo in repos">
<div class="row">
<div class="col flex-center">
<div>
<h4 class="repo-name">{{ (repo.description != null) ? repo.description :
repo.full_name }}</h4>
<div>⭐ {{ repo.stargazers_count }}</div>
</div>
</div>
<div class="col-auto">
<span v-if="themesInstalled.includes(repo.full_name.toLowerCase())"
class="codicon codicon-cloud-download"></span>
</div>
</div>
</li>
</ul>
</div>
<div class="github-preview" v-if="openRepo.full_name">
<div class="gh-preview-header">
<div class="row nopadding">
<div class="col nopadding flex-center">
<div>
<h3 class="repo-preview-name">{{ openRepo.description }}</h3>
<div>
<div class="svg-icon inline"
:style="{'--url': 'url(\'./assets/github.svg\')'}"></div>
<a class="repo-url" target="_blank" :href="openRepo.html_url">{{ openRepo.full_name
}}</a></div>
<div>⭐ {{ openRepo.stargazers_count }}</div>
</div>
</div>
<div class="col-auto nopadding flex-center">
<button class="md-btn md-btn-primary" @click="installThemeRepo(openRepo)">
<span v-if="!themesInstalled.includes(openRepo.full_name.toLowerCase())">{{$root.getLz('action.install')}}</span>
<span v-else>{{$root.getLz('action.update')}}</span>
</button>
</div>
</div>
</div>
<hr>
<div v-html="openRepo.readme" class="github-content"></div>
</div>
<div class="github-preview" v-else>
</div>
</transition>
</div>
</div>
</script>
<script>
Vue.component('themes-github', {
template: "#themes-github",
props: [],
data: function () {
return {
repos: [],
openRepo: {
id: -1,
name: '',
description: '',
html_url: '',
stargazers_count: 0,
owner: {
avatar_url: ''
},
readme: ""
},
themesInstalled: [],
themes: []
}
},
mounted() {
this.themes = ipcRenderer.sendSync("get-themes")
this.getRepos();
this.getInstalledThemes();
},
methods: {
openThemesFolder() {
ipcRenderer.invoke("open-path", "themes")
},
getInstalledThemes() {
let self = this
const themes = ipcRenderer.sendSync("get-themes")
// for each theme, get the github_repo property and push it to the themesInstalled array, if not blank
themes.forEach(theme => {
if (theme.github_repo !== "" && typeof theme.commit != "") {
self.themesInstalled.push(theme.github_repo.toLowerCase())
}
})
},
showRepo(repo) {
const self = this
const readmeUrl = `https://raw.githubusercontent.com/${repo.full_name}/main/README.md`;
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch(readmeUrl, requestOptions)
.then(response => response.text())
.then(result => {
self.openRepo = repo
self.openRepo.readme = self.convertReadMe(result);
})
.catch(error => {
self.openRepo = repo
self.openRepo.readme = `This repository doesn't have a README.md file.`;
console.log('error', error)
});
},
convertReadMe(text) {
return marked.parse(text)
},
installThemeRepo(repo) {
let self = this
let msg = app.stringTemplateParser(app.getLz('settings.option.visual.theme.github.install.confirm'), {
repo: repo.full_name
});
app.confirm(msg, (res) => {
if (res) {
ipcRenderer.once("theme-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
self.getInstalledThemes()
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
}
});
ipcRenderer.invoke("get-github-theme", repo.html_url)
}
})
},
installThemeURL() {
let self = this
app.prompt(app.getLz('settings.prompt.visual.theme.github.URL'), (result) => {
if (result) {
ipcRenderer.once("theme-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
}
});
ipcRenderer.invoke("get-github-theme", result)
}
});
},
getRepos() {
let self = this
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch("https://api.github.com/search/repositories?q=topic:cidermusictheme fork:true&per_page=100", requestOptions)
.then(response => response.text())
.then(result => {
let items = JSON.parse(result).items
self.repos = items
})
.catch(error => console.log('error', error));
}
}
})
</script>

View file

@ -0,0 +1,367 @@
<script type="text/x-template" id="installed-themes">
<div class="installed-themes-page">
<div class="gh-header">
<div class="row">
<div class="col nopadding">
<h1 class="header-text">
{{ $root.getLz("settings.option.visual.theme.manageStyles") }}
</h1>
</div>
<div class="col-auto nopadding flex-center">
<button class="md-btn md-btn-small md-btn-block" @click="$root.openSettingsPage('github-themes')">
{{$root.getLz('settings.option.visual.theme.github.explore')}}
</button>
</div>
<div class="col-auto flex-center">
<button class="md-btn md-btn-small md-btn-block" @click="$root.checkForThemeUpdates()">
{{ $root.getLz('settings.option.visual.theme.checkForUpdates') }}
</button>
</div>
<div class="col-auto nopadding flex-center">
<button class="md-btn md-btn-small md-btn-block" @click="openThemesFolder()">
{{$root.getLz('settings.option.visual.theme.github.openfolder')}}
</button>
</div>
</div>
</div>
<div class="gh-content">
<div class="repos-list">
<div class="repo-header">
<h4>{{$root.getLz('settings.option.visual.theme.github.available')}}</h4>
</div>
<ul class="list-group list-group-flush">
<template v-for="theme in themes">
<li @click="addStyle(theme.file)"
@contextmenu="contextMenu($event, theme)"
class="list-group-item list-group-item-dark"
:class="{'applied': $root.cfg.visual.styles.includes(theme.file)}">
<b-row>
<b-col class="themeLabel">{{theme.name}}</b-col>
<template v-if="$root.cfg.visual.styles.includes(theme.file)">
<b-col sm="auto" v-if="theme.pack">
<button class="themeContextMenu codicon codicon-package"></button>
</b-col>
<b-col sm="auto">
<button class="themeContextMenu codicon codicon-check"></button>
</b-col>
</template>
<template v-else>
<b-col sm="auto" v-if="theme.pack">
<button class="themeContextMenu codicon codicon-package"></button>
</b-col>
<b-col sm="auto">
<button @click.stop="contextMenu($event, theme)" class="themeContextMenu codicon codicon-list-unordered"></button>
</b-col>
</template>
</b-row>
</li>
<li @click="addStyle(packEntry.file)"
@contextmenu="contextMenu($event, theme)"
class="list-group-item list-group-item-dark addon"
v-for="packEntry in theme.pack"
:class="{'applied': $root.cfg.visual.styles.includes(packEntry.file)}"
v-if="theme.pack">
<b-row>
<b-col class="themeLabel">{{packEntry.name}}</b-col>
<template v-if="$root.cfg.visual.styles.includes(packEntry.file)">
<b-col sm="auto">
<button class="themeContextMenu codicon codicon-check"></button>
</b-col>
</template>
<template v-else>
<b-col sm="auto">
<button class="themeContextMenu codicon codicon-diff-added"></button>
</b-col>
</template>
</b-row>
</li>
</template>
</ul>
</div>
<div class="style-editor-container">
<div class="repo-header">
<h4>{{ $root.getLz("settings.option.visual.theme.github.applied") }} </h4>
</div>
<stylestack-editor ref="stackEditor" v-if="themes.length != 0" :themes="themes"/>
</div>
</div>
</div>
</script>
<script>
// do not translate
Vue.component('stylestack-editor', {
/*html*/
template: `
<div class="stylestack-editor" >
<draggable class="list-group" v-model="$root.cfg.visual.styles" @end="$root.reloadStyles()">
<b-list-group-item variant="dark" v-for="theme in $root.cfg.visual.styles" :key="theme">
<b-row>
<b-col sm="auto">
<div class="handle codicon codicon-grabber"></div>
</b-col>
<b-col class="themeLabel">{{getThemeName(theme)}}</b-col>
<b-col sm="auto">
<button class="removeItem codicon codicon-close" @click="remove(theme)"></button>
</b-col>
</b-row>
</b-list-group-item>
</draggable>
</div>
`,
props: {
themes: {
type: Array,
default: [],
required: true
}
},
data: function () {
return {
selected: null,
newTheme: null,
themeList: []
}
},
mounted() {
console.log(this.themes)
this.themeList = [...this.themes]
this.themeList.forEach(theme => {
if (theme.pack) {
theme.pack.forEach(packEntry => {
packEntry.file = theme.file.replace('index.less', '') + packEntry.file
this.themeList.push(packEntry)
})
}
})
},
methods: {
gitHubExplore() {
this.$root.openSettingsPage("github-themes")
},
getThemeName(filename) {
try {
return this.themeList.find(theme => theme.file === filename).name;
} catch (e) {
return filename;
}
},
moveUp() {
const styles = this.$root.cfg.visual.styles
const index = styles.indexOf(this.selected)
if (index > 0) {
styles.splice(index, 1)
styles.splice(index - 1, 0, this.selected)
}
this.$root.reloadStyles()
},
moveDown() {
const styles = this.$root.cfg.visual.styles
const index = styles.indexOf(this.selected)
if (index < styles.length - 1) {
styles.splice(index, 1)
styles.splice(index + 1, 0, this.selected)
}
this.$root.reloadStyles()
},
remove(style) {
const styles = this.$root.cfg.visual.styles
const index = styles.indexOf(style)
styles.splice(index, 1)
this.$root.reloadStyles()
},
addStyle(style) {
const styles = this.$root.cfg.visual.styles
styles.push(style)
this.$root.reloadStyles()
}
}
})
</script>
<script>
Vue.component('installed-themes', {
template: "#installed-themes",
props: [],
data: function () {
return {
repos: [],
openRepo: {
id: -1,
name: '',
description: '',
html_url: '',
stargazers_count: 0,
owner: {
avatar_url: ''
},
readme: ""
},
themesInstalled: [],
themes: []
}
},
mounted() {
this.getThemesList();
},
methods: {
getThemesList() {
let self = this
let themes = ipcRenderer.sendSync("get-themes")
themes.unshift({
name: "Acrylic Grain",
file: "grain.less"
})
themes.unshift({
name: "Sweetener",
file: "sweetener.less"
})
themes.unshift({
name: "Reduce Visuals",
file: "reduce_visuals.less"
})
// themes.unshift({
// name: "Inline Drawer",
// file: "inline_drawer.less"
// })
themes.unshift({
name: "Dark",
file: "dark.less"
})
this.themes = themes
},
contextMenu(event, theme) {
let self = this
let menu = {
items: {
"uninstall": {
name: app.getLz("settings.option.visual.theme.uninstall"),
disabled: true,
action: () => {
app.confirm(app.stringTemplateParser(app.getLz("settings.prompt.visual.theme.uninstallTheme"), {
theme: theme.name ?? theme.file
}), (res) => {
if (res) {
console.debug(theme)
ipcRenderer.once("theme-uninstalled", (event, args) => {
console.debug(event, args)
self.getThemesList()
})
ipcRenderer.invoke("uninstall-theme", theme.path)
}
})
}
},
"viewInfo": {
name: app.getLz("settings.option.visual.theme.viewInfo"),
disabled: true,
action: () => {
}
}
}
}
if (theme.path) {
menu.items.uninstall.disabled = false
}
this.$root.showMenuPanel(menu, event)
},
openThemesFolder() {
ipcRenderer.invoke("open-path", "themes")
},
getInstalledThemes() {
let self = this
const themes = ipcRenderer.sendSync("get-themes")
// for each theme, get the github_repo property and push it to the themesInstalled array, if not blank
themes.forEach(theme => {
if (theme.github_repo !== "" && typeof theme.commit != "") {
self.themesInstalled.push(theme.github_repo.toLowerCase())
}
})
},
addStyle(filename) {
this.$refs.stackEditor.addStyle(filename)
},
showRepo(repo) {
const self = this
const readmeUrl = `https://raw.githubusercontent.com/${repo.full_name}/main/README.md`;
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch(readmeUrl, requestOptions)
.then(response => response.text())
.then(result => {
self.openRepo = repo
self.openRepo.readme = self.convertReadMe(result);
})
.catch(error => {
self.openRepo = repo
self.openRepo.readme = `This repository doesn't have a README.md file.`;
console.log('error', error)
});
},
convertReadMe(text) {
return marked.parse(text)
},
installThemeRepo(repo) {
let self = this
let msg = app.stringTemplateParser(app.getLz('settings.option.visual.theme.github.install.confirm'), {
repo: repo.full_name
});
app.confirm(msg, (res) => {
if (res) {
ipcRenderer.once("theme-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
self.getInstalledThemes()
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
}
});
ipcRenderer.invoke("get-github-theme", repo.html_url)
}
})
},
installThemeURL() {
let self = this
app.prompt(app.getLz('settings.prompt.visual.theme.github.URL'), (result) => {
if (result) {
ipcRenderer.once("theme-installed", (event, arg) => {
if (arg.success) {
self.themes = ipcRenderer.sendSync("get-themes")
notyf.success(app.getLz('settings.notyf.visual.theme.install.success'));
} else {
notyf.error(app.getLz('settings.notyf.visual.theme.install.error'));
}
});
ipcRenderer.invoke("get-github-theme", result)
}
});
},
getRepos() {
let self = this
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch("https://api.github.com/search/repositories?q=topic:cidermusictheme fork:true", requestOptions)
.then(response => response.text())
.then(result => {
let items = JSON.parse(result).items
self.repos = items
})
.catch(error => console.log('error', error));
}
}
})
</script>

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
<script type="text/x-template" id="sidebar-playlist"> <script type="text/x-template" id="sidebar-playlist">
<div class="sidebar-playlist" :key="item.id"> <div class="sidebar-playlist" :key="item.id">
<button class="app-sidebar-item app-sidebar-item-playlist" :key="item.id" <button class="app-sidebar-item app-sidebar-item-playlist" :key="item.id"
:class="item.type != 'library-playlist-folders' ? {'active': $root.page.includes(item.id)} : [{'folder-button-active': folderOpened}, isPlaylistSelected]" :class="item.type != 'library-playlist-folders' ? {'active': $root.page.includes(item.id)} : ['playlist-folder', {'folder-button-active': folderOpened}, isPlaylistSelected]"
@contextmenu="playlistContextMenu($event, item.id)" @contextmenu="playlistContextMenu($event, item.id)"
@dragstart="startDrag($event, item)" @dragstart="startDrag($event, item)"
@dragover="dragOver" @dragover="dragOver"
@ -9,7 +9,7 @@
:href="item.href" :href="item.href"
@click='clickEvent()'> @click='clickEvent()'>
<template v-if="!renaming"> <template v-if="!renaming">
<div class="sidebar-icon" :key="item.id" v-html="icon"></div> {{ item.attributes.name }} <svg-icon :url="icon" name="sidebar-playlist"/> {{ item.attributes.name }}
<small class="presentNotice" v-if="hasRelatedMediaItems">(Track present)</small> <small class="presentNotice" v-if="hasRelatedMediaItems">(Track present)</small>
</template> </template>
<input type="text" v-model="item.attributes.name" class="pl-rename-field" @blur="rename()" @keydown.enter="rename()" v-else> <input type="text" v-model="item.attributes.name" class="pl-rename-field" @blur="rename()" @keydown.enter="rename()" v-else>
@ -57,9 +57,9 @@
}, },
async mounted() { async mounted() {
if (this.item.type !== "library-playlist-folders") { if (this.item.type !== "library-playlist-folders") {
this.icon = await this.$root.getSvgIcon("./assets/feather/list.svg") this.icon = ("./assets/feather/list.svg")
} else { } else {
this.icon = await this.$root.getSvgIcon("./assets/feather/folder.svg") this.icon = ("./assets/feather/folder.svg")
} }
let playlistMap = this.$root.playlists.trackMapping let playlistMap = this.$root.playlists.trackMapping
if (this.relateMediaItems.length != 0) { if (this.relateMediaItems.length != 0) {
@ -126,9 +126,9 @@
} }
}) })
if (typeof this.$parent.getChildren == "function") { if (typeof this.$root.getChildren == "function") {
this.$parent.getChildren() this.$root.getChildren()
console.log(this.$parent.children) console.log(this.$root.children)
} }
await this.getChildren() await this.getChildren()
this.$root.sortPlaylists() this.$root.sortPlaylists()
@ -207,7 +207,44 @@
openPlaylist(item) { openPlaylist(item) {
this.$root.appRoute(`playlist_` + item.id); this.$root.appRoute(`playlist_` + item.id);
this.$root.showingPlaylist = []; this.$root.showingPlaylist = [];
if (item.id == 'ciderlocal') {
this.$root.showingPlaylist = {
"id": "ciderlocal",
"type": "library-playlists",
"href": "",
"attributes": {
"artwork": {
"width": null,
"height": null,
"url": "",
"hasP3": false
},
"dateAdded": "2021-02-16T03:39:47Z",
"name": "Local Songs",
"canDelete": true,
"hasCatalog": true,
"canEdit": true,
"playParams": {
"id": "ciderlocal",
"kind": "playlist",
"isLibrary": true,
},
"isPublic": true,
"description": {
"standard": ""
}
},
"relationships": {
"tracks": {
"href": "",
"data": this.$root.library.localsongs
}
}
}
this.$root.playlists.loadingState = 1;
} else {
this.$root.getPlaylistFromID(this.$root.page.substring(9), true) this.$root.getPlaylistFromID(this.$root.page.substring(9), true)
}
}, },
getPlaylistChildren(item) { getPlaylistChildren(item) {
let self = this let self = this

View file

@ -0,0 +1,316 @@
<script type="text/x-template" id="cider-app-sidebar">
<div id="app-sidebar">
<template v-if="$root.getThemeDirective('windowLayout') != 'twopanel'">
<div class="app-sidebar-header">
<div class="search-input-container">
<div class="search-input--icon"></div>
<input
type="search"
spellcheck="false"
@click="$root.showSearch()"
@focus="$root.search.showHints = true"
@blur="$root.setTimeout(()=>{$root.search.showHints = false}, 300)"
v-on:keyup.enter="$root.searchQuery();$root.search.showHints = false"
@change="$root.showSearch();"
@input="$root.getSearchHints()"
:placeholder="$root.getLz('term.search') + '...'"
v-model="$root.search.term"
ref="searchInput"
class="search-input"
/>
<div
class="search-hints-container"
v-if="$root.search.showHints && $root.search.hints.length != 0"
>
<div class="search-hints">
<button
class="search-hint text-overflow-elipsis"
v-for="hint in $root.search.hints"
@click="$root.search.term = hint;$root.search.showHints = false;$root.searchQuery(hint)"
>
{{ hint }}
</button>
</div>
</div>
</div>
</div>
</template>
<div class="app-sidebar-content" scrollaxis="y">
<!-- AM Navigation -->
<div v-show="$root.getThemeDirective('windowLayout') != 'twopanel'" class="sidebarCatalogSection">
<div
class="app-sidebar-header-text"
@click="$root.cfg.general.sidebarCollapsed.cider = !$root.cfg.general.sidebarCollapsed.cider"
:class="{collapsed: $root.cfg.general.sidebarCollapsed.cider}"
>
{{ $root.getLz("app.name") }}
</div>
<template v-if="!$root.cfg.general.sidebarCollapsed.cider">
<sidebar-library-item
:name="$root.getLz('home.title')"
svg-icon="./assets/feather/home.svg"
svg-icon-name="home"
page="home"
>
</sidebar-library-item>
</template>
<div
class="app-sidebar-header-text"
@click="$root.cfg.general.sidebarCollapsed.applemusic = !$root.cfg.general.sidebarCollapsed.applemusic"
:class="{collapsed: $root.cfg.general.sidebarCollapsed.applemusic}"
>
{{ $root.getLz("term.appleMusic") }}
</div>
<template v-if="!$root.cfg.general.sidebarCollapsed.applemusic">
<sidebar-library-item
:name="$root.getLz('term.listenNow')"
svg-icon="./assets/feather/play-circle.svg"
svg-icon-name="listenNow"
page="listen_now"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.browse')"
svg-icon="./assets/feather/globe.svg"
svg-icon-name="browse"
page="browse"
>
</sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.radio')"
svg-icon="./assets/feather/radio.svg"
svg-icon-name="radio"
page="radio"
></sidebar-library-item>
</template>
</div>
<div
class="app-sidebar-header-text"
@click="$root.cfg.general.sidebarCollapsed.library = !$root.cfg.general.sidebarCollapsed.library"
:class="{collapsed: $root.cfg.general.sidebarCollapsed.library}"
>
{{ $root.getLz("term.library") }}
</div>
<template v-if="!$root.cfg.general.sidebarCollapsed.library">
<sidebar-library-item
:name="$root.getLz('term.recentlyAdded')"
svg-icon="./assets/feather/plus-circle.svg"
svg-icon-name="recentlyAdded"
v-if="$root.cfg.general.sidebarItems.recentlyAdded"
page="library-recentlyadded"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.songs')"
svg-icon="./assets/feather/music.svg"
svg-icon-name="songs"
v-if="$root.cfg.general.sidebarItems.songs"
page="library-songs"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.albums')"
svg-icon="./assets/feather/disc.svg"
svg-icon-name="albums"
v-if="$root.cfg.general.sidebarItems.albums"
page="library-albums"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.artists')"
svg-icon="./assets/feather/user.svg"
svg-icon-name="artists"
v-if="$root.cfg.general.sidebarItems.artists"
page="library-artists"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.videos')"
svg-icon="./assets/feather/video.svg"
svg-icon-name="videos"
v-if="$root.cfg.general.sidebarItems.videos"
page="library-videos"
></sidebar-library-item>
<sidebar-library-item
:name="$root.getLz('term.podcasts')"
svg-icon="./assets/feather/mic.svg"
svg-icon-name="podcasts"
v-if="$root.cfg.general.sidebarItems.podcasts"
page="podcasts"
>
</sidebar-library-item>
</template>
<template v-if="$root.cfg.libraryPrefs.localPaths.length != 0 && $root.cfg.advanced.experiments.includes('localLibrary')">
<div class="app-sidebar-header-text"
@click="$root.cfg.general.sidebarCollapsed.localLibrary = !$root.cfg.general.sidebarCollapsed.localLibrary"
:class="{collapsed: $root.cfg.general.sidebarCollapsed.localLibrary}">
Local Library
</div>
<template v-if="!$root.cfg.general.sidebarCollapsed.localLibrary">
<sidebar-playlist :item="{attributes: { name:'Songs'} , id:'ciderlocal'}"></sidebar-playlist>
</template>
</template>
<template v-if="$root.getPlaylistFolderChildren('p.applemusic').length != 0">
<div
class="app-sidebar-header-text"
@click="$root.cfg.general.sidebarCollapsed.amplaylists = !$root.cfg.general.sidebarCollapsed.amplaylists"
@contextmenu="$root.playlistHeaderContextMenu"
:class="{collapsed: $root.cfg.general.sidebarCollapsed.amplaylists}"
>
{{ $root.getLz("term.appleMusic") }}
{{ $root.getLz("term.playlists") }}
</div>
<template v-if="!$root.cfg.general.sidebarCollapsed.amplaylists">
<sidebar-playlist
v-for="item in $root.getPlaylistFolderChildren('p.applemusic')"
:item="item"
>
</sidebar-playlist>
</template>
</template>
<div
class="app-sidebar-header-text"
@click="$root.cfg.general.sidebarCollapsed.playlists = !$root.cfg.general.sidebarCollapsed.playlists"
@contextmenu="$root.playlistHeaderContextMenu"
:class="{collapsed: $root.cfg.general.sidebarCollapsed.playlists}"
>
{{ $root.getLz("term.playlists") }}
</div>
<template v-if="!$root.cfg.general.sidebarCollapsed.playlists">
<button class="app-sidebar-item" @click="$root.playlistHeaderContextMenu">
<svg-icon url="./assets/feather/plus.svg"></svg-icon>
<div class="sidebar-item-text">
{{ $root.getLz("action.createNew") }}
</div>
</button>
<sidebar-playlist
v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')"
:item="item"
>
</sidebar-playlist>
</template>
</div>
<div class="app-sidebar-footer display--small app-sidebar-footer--controls">
<div
class="app-playback-controls"
v-if="$root.mkReady()"
@contextmenu="$root.nowPlayingContextMenu"
>
<div class="control-buttons">
<div class="app-chrome-item">
<button
class="playback-button--small shuffle"
v-if="$root.mk.shuffleMode == 0"
@click="$root.mk.shuffleMode = 1"
:title="$root.getLz('term.enableShuffle')"
:class="$root.isDisabled() && 'disabled'"
v-b-tooltip.hover.righttop
></button>
<button
class="playback-button--small shuffle active"
v-else
@click="$root.mk.shuffleMode = 0"
:title="$root.getLz('term.disableShuffle')"
:class="$root.isDisabled() && 'disabled'"
v-b-tooltip.hover.righttop
></button>
</div>
<div class="app-chrome-item">
<button
class="playback-button previous"
@click="$root.prevButton()"
:class="$root.isPrevDisabled() && 'disabled'"
:title="$root.getLz('term.previous')"
v-b-tooltip.hover
></button>
</div>
<div class="app-chrome-item">
<button class="playback-button stop" @click="$root.mk.stop()"
v-if="$root.mk.isPlaying && $root.mk.nowPlayingItem.attributes.playParams.kind == 'radioStation'"
:title="$root.getLz('term.stop')" v-b-tooltip.hover></button>
<button class="playback-button pause" @click="$root.mk.pause()"
v-else-if="$root.mk.isPlaying"
:title="$root.getLz('term.pause')" v-b-tooltip.hover></button>
<button class="playback-button play" @click="$root.mk.play()" v-else
:title="$root.getLz('term.play')"
v-b-tooltip.hover></button>
</div>
<div class="app-chrome-item">
<button
class="playback-button next"
@click="$root.skipToNextItem()"
:title="$root.getLz('term.next')"
:class="$root.isNextDisabled() && 'disabled'"
v-b-tooltip.hover
></button>
</div>
<div class="app-chrome-item">
<button
class="playback-button--small repeat"
v-if="$root.mk.repeatMode == 0"
@click="$root.mk.repeatMode = 1"
:class="$root.isDisabled() && 'disabled'"
:title="$root.getLz('term.enableRepeatOne')"
v-b-tooltip.hover
></button>
<button
class="playback-button--small repeat repeatOne"
@click="$root.mk.repeatMode = 2"
v-else-if="$root.mk.repeatMode == 1"
:title="$root.getLz('term.disableRepeatOne')"
:class="$root.isDisabled() && 'disabled'"
v-b-tooltip.hover
></button>
<button
class="playback-button--small repeat active"
@click="$root.mk.repeatMode = 0"
v-else-if="$root.mk.repeatMode == 2"
:title="$root.getLz('term.disableRepeat')"
:class="$root.isDisabled() && 'disabled'"
v-b-tooltip.hover
></button>
</div>
</div>
<div class="app-chrome-item volume">
<div class="input-container">
<button
class="volume-button--small volume"
@click="$root.muteButtonPressed()"
:class="{'active': $root.cfg.audio.volume == 0}"
:title="$root.cfg.audio.muted ? $root.getLz('term.unmute') : $root.getLz('term.mute')"
v-b-tooltip.hover
></button>
<input
type="range"
class=""
@wheel="$root.volumeWheel"
:step="$root.cfg.audio.volumeStep"
min="0"
:max="$root.cfg.audio.maxVolume"
v-model="$root.mk.volume"
v-if="typeof $root.mk.volume != 'undefined'"
@change="$root.checkMuteChange()"
v-b-tooltip.hover
:title="$root.formatVolumeTooltip()"
/>
</div>
</div>
</div>
</div>
<div
class="app-sidebar-notification backgroundNotification"
v-if="$root.library.backgroundNotification.show"
>
<div class="message">
{{ $root.library.backgroundNotification.message }} ({{
$root.library.backgroundNotification.progress
}}
/ {{ $root.library.backgroundNotification.total }})
</div>
</div>
</div>
</script>
<script>
Vue.component("cider-app-sidebar", {
template: "#cider-app-sidebar"
})
</script>

View file

@ -2,23 +2,23 @@
<html lang="en"> <html lang="en">
<head> <head>
<link rel="preconnect" href="https://amp-api.music.apple.com/" crossorigin /> <link rel="preconnect" href="https://amp-api.music.apple.com/" crossorigin/>
<link rel="preconnect" href="https://api.music.apple.com/" crossorigin /> <link rel="preconnect" href="https://api.music.apple.com/" crossorigin/>
<link rel="preconnect" href="https://is1-ssl.mzstatic.com/" crossorigin /> <link rel="preconnect" href="https://is1-ssl.mzstatic.com/" crossorigin/>
<link rel="preconnect" href="https://is2-ssl.mzstatic.com/" crossorigin /> <link rel="preconnect" href="https://is2-ssl.mzstatic.com/" crossorigin/>
<link rel="preconnect" href="https://is3-ssl.mzstatic.com/" crossorigin /> <link rel="preconnect" href="https://is3-ssl.mzstatic.com/" crossorigin/>
<link rel="preconnect" href="https://is4-ssl.mzstatic.com/" crossorigin /> <link rel="preconnect" href="https://is4-ssl.mzstatic.com/" crossorigin/>
<link rel="preconnect" href="https://is5-ssl.mzstatic.com/" crossorigin /> <link rel="preconnect" href="https://is5-ssl.mzstatic.com/" crossorigin/>
<link rel="preconnect" href="https://play.itunes.apple.com/" crossorigin /> <link rel="preconnect" href="https://play.itunes.apple.com/" crossorigin/>
<link rel="preconnect" href="https://aod-ssl.itunes.apple.com/" crossorigin /> <link rel="preconnect" href="https://aod-ssl.itunes.apple.com/" crossorigin/>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Cider</title> <title>Cider</title>
<link rel="stylesheet/less" type="text/css" href="style.less" /> <link rel="<%- (env.dev ? "stylesheet" : "stylesheet/less") %>" type="text/css" href="style.<%- (env.dev ? "css" : "less") %>"/>
<link rel="stylesheet/less" type="text/css" id="userTheme" href="themes/default.less" /> <!-- <link rel="stylesheet/less" type="text/css" id="userTheme" href="themes/default.less"/>-->
<script src="./lib/less.js"></script> <script src="./lib/less.js"></script>
<script src="<%- (env.dev ? " ./lib/vue.js" : "./lib/vue.dev.js" ) %>"></script> <script src="<%- (env.dev ? " ./lib/vue.js" : "./lib/vue.dev.js") %>"></script>
<script src="./lib/vue-horizontal.js"></script> <script src="./lib/vue-horizontal.js"></script>
<script src="./lib/smoothscroll.js"></script> <script src="./lib/smoothscroll.js"></script>
<script src="./lib/vuex.min.js"></script> <script src="./lib/vuex.min.js"></script>
@ -55,7 +55,7 @@
align-items: center; align-items: center;
} }
#LOADER>svg { #LOADER > svg {
width: 128px; width: 128px;
} }
@ -67,11 +67,16 @@
</style> </style>
</head> </head>
<body class="notransparency" oncontextmenu="return false;" loading="1" os-release="<%= parseInt(env.osRelease) %>" platform="<%= env.platform %>"> <body class="notransparency" oncontextmenu="return false;" loading="1" os-release="<%= parseInt(env.osRelease) %>"
<div id="LOADER"> platform="<%= env.platform %>">
<div id="LOADER">
<%- include("../assets/cider-round.svg") %> <%- include("../assets/cider-round.svg") %>
</div> </div>
<div id="app" :class="getAppClasses()" :style="getAppStyle()" :library-visbile="(chrome.sidebarCollapsed ? 0 : 1)" :window-style="cfg.visual.directives.windowLayout"> <div id="app" :class="getAppClasses()"
v-if="appVisible"
:window-state="chrome.windowState"
:style="getAppStyle()" :library-visible="(chrome.sidebarCollapsed ? 0 : 1)"
:window-style="cfg.visual.directives.windowLayout">
<transition name="fsModeSwitch"> <transition name="fsModeSwitch">
<div id="app-main" v-show="appMode == 'player'"> <div id="app-main" v-show="appMode == 'player'">
<%- include('app/chrome-top'); %> <%- include('app/chrome-top'); %>
@ -81,13 +86,13 @@
</transition> </transition>
<transition name="fsModeSwitch"> <transition name="fsModeSwitch">
<div class="fullscreen-view-container" v-if="appMode == 'fullscreen'"> <div class="fullscreen-view-container" v-if="appMode == 'fullscreen'">
<fullscreen-view :image="currentArtUrlRaw" :time="lyriccurrenttime" :lyrics="lyrics" <fullscreen-view :image="currentArtUrlRaw" :time="mk.currentPlaybackTime - lyricOffset" :lyrics="lyrics"
:richlyrics="richlyrics"></fullscreen-view> :richlyrics="richlyrics"></fullscreen-view>
</div> </div>
</transition> </transition>
<transition name="fsModeSwitch"> <transition name="fsModeSwitch">
<div class="fullscreen-view-container" v-if="appMode == 'mini'"> <div class="fullscreen-view-container" v-if="appMode == 'mini'">
<mini-view :image="currentArtUrlRaw" :time="lyriccurrenttime" :lyrics="lyrics" :richlyrics="richlyrics"> <mini-view :image="currentArtUrlRaw" :time="mk.currentPlaybackTime - lyricOffset" :lyrics="lyrics" :richlyrics="richlyrics">
</mini-view> </mini-view>
</div> </div>
</transition> </transition>
@ -98,28 +103,20 @@
</transition> </transition>
<%- include('app/panels'); %> <%- include('app/panels'); %>
<div class="cursor" v-if="chrome.showCursor"></div> <div class="cursor" v-if="chrome.showCursor"></div>
</div> </div>
<% for(var i=0; i < Object.keys(env.components).length ; i++) {%> <% for(var i = 0; i < Object.keys(env.components).length ; i++) { %>
<%- include(env.components[i]); %> <%- include(env.components[i]); %>
<% } %> <% } %>
<script async
<script async src="<%- (env.useV3 ? "https://js-cdn.music.apple.com/musickit/v3/amp/musickit.js" : "https://js-cdn.music.apple.com/musickit/v2/amp/musickit.js" ) %>" data-web-components> src="<%- (env.useV3 ? 'https://js-cdn.music.apple.com/musickit/v3/amp/musickit.js' : 'https://api.cider.sh/musickit.js') %>"
</script> data-web-components>
<script src="index.js?v=1"></script>
<script type="text/x-template" id="am-musiccovershelf">
<h1>{{ component.attributes.title.stringForDisplay }}</h1>
</script> </script>
<script src="index.js?v=1"></script>
<!-- Sidebar Item --> <script type="text/x-template" id="am-musiccovershelf">
<script type="text/x-template" id="sidebar-library-item"> <h1>{{ component.attributes.title.stringForDisplay }}</h1>
<button class="app-sidebar-item"
:class="$parent.getSidebarItemClass(page)" @click="$root.setWindowHash(page)">
<div class="sidebar-icon" v-html="svgIconData" v-if="svgIconData != ''"></div>
{{ name }}
</button>
</script> </script>
</body> </body>

View file

@ -12,30 +12,35 @@
{{$root.getLz('term.rightsReserved')}}</p> {{$root.getLz('term.rightsReserved')}}</p>
<hr> <hr>
<h3>{{$root.getLz('term.sponsor')}}</h3> <h3>{{$root.getLz('term.sponsor')}}</h3>
<button onclick="window.open('https://github.com/sponsors/ciderapp')" class="md-btn sponsorBtn"><img src="./assets/github.svg"/>GitHub Sponsors</button> <button onclick="window.open('https://github.com/sponsors/ciderapp')" class="md-btn sponsorBtn githubBtn"><img src="./assets/github.svg"/>GitHub Sponsors</button>
<button onclick="window.open('https://ko-fi.com/cryptofyre')" class="md-btn sponsorBtn"><img src="./assets/ko_fi.svg"/>Ko-fi</button> <button onclick="window.open('https://ko-fi.com/cryptofyre')" class="md-btn sponsorBtn kofiBtn"><img src="./assets/ko_fi.svg"/>Ko-fi</button>
<button onclick="window.open('https://opencollective.com/ciderapp')" class="md-btn sponsorBtn"><img src="./assets/open_collective.svg"/>Open Collective</button> <button onclick="window.open('https://opencollective.com/ciderapp')" class="md-btn sponsorBtn opencollectiveBtn"><img src="./assets/open_collective.svg"/>Open Collective</button>
<h3>{{$root.getLz('term.socials')}}</h3> <h3>{{$root.getLz('term.socials')}}</h3>
<button onclick="window.open('https://github.com/ciderapp/Cider')" class="md-btn sponsorBtn"><img src="./assets/github.svg"/>{{$root.getLz('term.github')}}</button> <button onclick="window.open('https://github.com/ciderapp/Cider')" class="md-btn sponsorBtn githubBtn"><img src="./assets/github.svg"/>{{$root.getLz('term.github')}}</button>
<button onclick="window.open('https://discord.gg/applemusic')" class="md-btn sponsorBtn"><img style="height: 26px;" src="./assets/discord.svg"/>{{$root.getLz('term.discord')}}</button> <button onclick="window.open('https://discord.gg/applemusic')" class="md-btn sponsorBtn discordBtn"><img style="height: 26px;" src="./assets/discord.svg"/>{{$root.getLz('term.discord')}}</button>
<button onclick="window.open('https://twitter.com/UseCider')" class="md-btn sponsorBtn"><img src="./assets/twitter.svg"/>Twitter</button> <button onclick="window.open('https://twitter.com/UseCider')" class="md-btn sponsorBtn twitterBtn"><img src="./assets/twitter.svg"/>Twitter</button>
</div> </div>
<div class="col"> <div class="col">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<h3>{{$root.getLz('term.ciderTeam')}}</h3> <h3>{{$root.getLz('term.ciderTeam')}}</h3>
<button class="md-btn teamBtn" @click="window.open(member.link)" v-for="member in team"> <div class="md-btn teamBtn" v-for="member in team" @click="window.open(member.link)">
<img :src="member.avatar"/> <img :src="member.avatar"/>
<div class="row" style="width:100%;"> <div class="row" style="width:100%;">
<div class="col" style="text-align: left"> <div class="col" style="text-align: left;">
{{ member.name }} {{ member.name }}
</div> </div>
<div class="col" style="text-align: right">
<button @click.stop="window.open(member.twitter)" class="social-btn" v-if="member.twitter">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="white" style=""><path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"></path></svg>
</button>
</div>
<div class="col-auto"> <div class="col-auto">
<b>{{ member.role }}</b> <b>{{ member.role }}</b>
</div> </div>
</div> </div>
</button> </div>
</div> </div>
</div> </div>
</div> </div>
@ -64,25 +69,29 @@
name: 'cryptofyre', name: 'cryptofyre',
link: 'https://github.com/cryptofyre', link: 'https://github.com/cryptofyre',
role: app.getLz('term.developer'), role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/33162551?v=4' avatar: 'https://avatars.githubusercontent.com/u/33162551?v=4',
twitter: 'https://twitter.com/cryptofyre'
}, },
{ {
name: 'Core', name: 'Core',
link: 'https://github.com/coredev-uk', link: 'https://github.com/coredev-uk',
role: app.getLz('term.developer'), role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/64542347?v=4' avatar: 'https://avatars.githubusercontent.com/u/64542347?v=4',
twitter: 'https://twitter.com/core_hdd'
}, },
{ {
name: 'Quacksire', name: 'Quacksire',
link: 'https://github.com/quacksire', link: 'https://github.com/quacksire',
role: app.getLz('term.developer'), role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/19170969?v=4' avatar: 'https://avatars.githubusercontent.com/u/19170969?v=4',
twitter: 'https://twitter.com/duckdoquack'
}, },
{ {
name: 'booploops', name: 'booploops',
link: 'https://github.com/booploops', link: 'https://github.com/booploops',
role: app.getLz('term.developer'), role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/49113086?v=4' avatar: 'https://avatars.githubusercontent.com/u/49113086?v=4',
twitter: 'https://twitter.com/boopl00ps'
}, },
{ {
name: 'vapormusic', name: 'vapormusic',
@ -94,25 +103,29 @@
name: 'crypticplank', name: 'crypticplank',
link: 'https://github.com/crypticplank', link: 'https://github.com/crypticplank',
role: app.getLz('term.developer'), role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/52553007?v=4' avatar: 'https://avatars.githubusercontent.com/u/52553007?v=4',
twitter: 'https://twitter.com/crypticplank'
}, },
{ {
name: 'Maikiwi', name: 'Maikiwi',
link: 'https://github.com/maikirakiwi', link: 'https://github.com/maikirakiwi',
role: app.getLz('term.developer'), role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/74925636?v=4' avatar: 'https://avatars.githubusercontent.com/u/74925636?v=4',
twitter: 'https://twitter.com/notmaikiwi'
}, },
{ {
name: 'yazninja', name: 'yazninja',
link: 'https://github.com/yazninja', link: 'https://github.com/yazninja',
role: app.getLz('term.developer'), role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/71800112?v=4' avatar: 'https://avatars.githubusercontent.com/u/71800112?v=4',
twitter: 'https://twitter.com/YazNinjaa'
}, },
{ {
name: 'GamingLiamStudios', name: 'GamingLiamStudios',
link: 'https://github.com/GamingLiamStudios', link: 'https://github.com/GamingLiamStudios',
role: app.getLz('term.developer'), role: app.getLz('term.developer'),
avatar: 'https://avatars.githubusercontent.com/u/58615717?v=4' avatar: 'https://avatars.githubusercontent.com/u/58615717?v=4',
twitter: 'https://twitter.com/GLStudios_'
}, },
{ {
name: 'Amaru', name: 'Amaru',

View file

@ -25,6 +25,7 @@
</div> </div>
<div class="col flex-center artist-title" <div class="col flex-center artist-title"
:class="{'artist-animation-on': (data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)) || hasHero() }" :class="{'artist-animation-on': (data.attributes.editorialVideo && (data.attributes.editorialVideo.motionArtistWide16x9 || data.attributes.editorialVideo.motionArtistFullscreen16x9)) || hasHero() }"
:style="{ 'color': '#' +hasHeroObject()?.textColor1 ?? ''}"
> >
<button class="artist-play" @click="app.mk.setStationQueue({artist:'a-'+data.id}).then(()=>{ <button class="artist-play" @click="app.mk.setStationQueue({artist:'a-'+data.id}).then(()=>{
app.mk.play() app.mk.play()
@ -178,11 +179,21 @@
return this.data.attributes?.editorialArtwork?.centeredFullscreenBackground.url return this.data.attributes?.editorialArtwork?.centeredFullscreenBackground.url
} else if(this.data.attributes?.editorialArtwork?.bannerUber) { } else if(this.data.attributes?.editorialArtwork?.bannerUber) {
return this.data.attributes?.editorialArtwork?.bannerUber.url return this.data.attributes?.editorialArtwork?.bannerUber.url
}else if(this.data.attributes?.editorialArtwork?.subscriptionHero){ } else if(this.data.attributes?.editorialArtwork?.subscriptionHero){
return this.data.attributes?.editorialArtwork?.subscriptionHero.url return this.data.attributes?.editorialArtwork?.subscriptionHero.url
} }
return false; return false;
}, },
hasHeroObject() {
if(this.data.attributes?.editorialArtwork?.centeredFullscreenBackground){
return this.data.attributes?.editorialArtwork?.centeredFullscreenBackground
} else if(this.data.attributes?.editorialArtwork?.bannerUber) {
return this.data.attributes?.editorialArtwork?.bannerUber
} else if(this.data.attributes?.editorialArtwork?.subscriptionHero){
return this.data.attributes?.editorialArtwork?.subscriptionHero
}
return [];
},
isHeaderVisible(visible) { isHeaderVisible(visible) {
this.headerVisible = visible this.headerVisible = visible
}, },

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