Merge pull request #1 from ciderapp/main

Pull changes
This commit is contained in:
Marc Molenaar 2022-09-21 19:52:15 +02:00 committed by GitHub
commit d63e29ea0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
84 changed files with 2551 additions and 1402 deletions

View file

@ -8,7 +8,7 @@ executors:
orbs: # Add orbs to your configuration
jira: circleci/jira@1.0.5 # invokes the Jira orb, making its commands accessible
gh: circleci/github-cli@2.1
# The jobs for this project
jobs:
prepare-build:
@ -111,14 +111,7 @@ jobs:
steps:
- attach_workspace:
at: ~/Cider/
- run:
name: Installing GitHub Command Line Interface
command: |
sudo apt-get update -y
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt-get update -y
sudo apt install -y gh
- gh/install
- run:
name: Move Build Files
command: |
@ -142,12 +135,13 @@ jobs:
echo "Attempting to create release for Cider v${APP_VERSION} on the ${CIRCLE_BRANCH} branch.";
if [[ "${APP_VERSION}" = *"beta"* ]]; then
echo $'**Beta Release**\nA full changelog is unavailable, but you can view the branch comparison [here](https://github.com/ciderapp/cider/compare/stable...main).\nThese builds are considered bleeding edge, expect bugs and please do not use this as a representation of the fu ll app.\nOur full support disclaimer can be found [here](https://docs.cider.sh/support/disclaimer#support-nightly-beta-releases).' > release-notes.md
gh release create "v${APP_VERSION}" --prerelease --title "Cider Version ${APP_VERSION} (${CIRCLE_BRANCH})" --notes-file release-notes.md -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
curl http://129.146.42.180/api/v1/github/sync/main
else
echo $'**Stable Release**\nA full changelog is unavailable, but you can view the branch comparison [here](https://github.com/ciderapp/cider/compare/stable...main).\nThese are the most stable builds we can provide. If you experience any issues, please report them [here](https://github.com/ciderapp/cider/issues/new).\nOur full support disclaimer can be found [here](https://docs.cider.sh/support/disclaimer#support-releases).' > release-notes.md
fi;
gh release create "v${APP_VERSION}" --title "Cider Version ${APP_VERSION} (${CIRCLE_BRANCH})" --notes-file release-notes.md -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
curl http://129.146.42.180/api/v1/github/sync/stable
fi;
# Orchestrate our job run sequence
workflows:

View file

@ -33,12 +33,30 @@ body:
description: |
Examples:
- **OS**: Ubuntu 20.04
- **App Version** and/or **Commit ID**: 1.0.0 c9d43be
- **Desktop Environment (if applicable)**: i3wm
- **App Version**: 1.5.5-beta.22
- **Commit ID (if available)**: c9d43be
To find app version, Cider Menu -> About
*To find the app version, Cider Menu -> About*
value: |
- OS:
- App Version and/or Commit ID:
- Desktop Environment:
- App Version:
- Commit ID:
validations:
required: true
- type: dropdown
id: download
attributes:
label: How did you download the software?
options:
- Microsoft Store
- GitHub
- Winget
- Winget (Nightly)
- Chocolatey
- Flathub
- AUR
validations:
required: true
- type: textarea

View file

@ -23,7 +23,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
node-version: [ 18 ]
steps:
- uses: maxim-lobanov/setup-xcode@v1
@ -34,6 +34,16 @@ jobs:
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2.2.2
with:
version: 7
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Run Version Script
env:
GH_REQUEST_TOKEN: ${{ secrets.RELEASE_TOKEN }}
@ -41,6 +51,9 @@ jobs:
sudo chmod +x resources/version.sh && ./resources/version.sh || true
echo "APP_VERSION=$(node -p -e 'require("./package.json").version')" >>$GITHUB_ENV
- name: Clear node_airtunes2 cache
run: rm -rf ~/Library/pnpm/store/v3/tmp/cf5bc2de2629636ca224995234b8eaa1 || true
- name: Sign in to EVS
run: |
python3 -m pip install --upgrade castlabs-evs
@ -49,12 +62,9 @@ jobs:
- name: Setup Environment
run: brew install automake #libtool autoconf
- name: Clear node_airtunes2 cache
run: sudo rm -rf /Users/runner/Library/Caches/Yarn/v6/.tmp/cf5bc2de2629636ca224995234b8eaa1 || true
- name: Install and Configure Node Modules
run: |
yarn install
pnpm install
cp resources/verror-types node_modules/@types/verror/index.d.ts
cp resources/macPackager.js node_modules/app-builder-lib/out/macPackager.js
rm -r node_modules/pouchdb-node/node_modules/leveldown || true
@ -70,7 +80,7 @@ jobs:
APPLE_ID_PASSWORD: ${{ secrets.APPLEIDPASS }}
PSC_NAME: ${{ secrets.PSC_NAME }}
DEVELOPER_DIR: /Applications/Xcode_12.4.app/Contents/Developer
run: yarn dist:universalNotWorking -p never
run: pnpm dist:universalNotWorking -p never
- name: Add license to DMG
run: npx dmg-license resources/license.json dist/*.dmg
@ -125,3 +135,16 @@ jobs:
repo_token: ${{ secrets.RELEASE_TOKEN }}
file: dist/Cider-${{ env.APP_VERSION }}-universal.pkg
tag: v${{ env.APP_VERSION }}
- name: Trigger Bot Sync (main)
uses: indiesdev/curl@v1.1
with:
url: http://129.146.42.180/api/v1/github/sync/main
log-response: true
timeout: 60000
- name: Trigger Bot Sync (stable)
uses: indiesdev/curl@v1.1
with:
url: http://129.146.42.180/api/v1/github/sync/stable
log-response: true
timeout: 60000

View file

@ -1,71 +0,0 @@
name: Cider Chores
on:
push:
branches:
- main
jobs:
update-lockfile:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [ 18 ]
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v2
with:
# Make sure the actual branch is checked out when running on pull requests
ref: ${{ github.head_ref }}
- uses: pnpm/action-setup@v2.2.2
with:
version: 7
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Update lockfile
run: pnpm i --lockfile-only
- name: Commit Updated Lockfile
uses: stefanzweifel/git-auto-commit-action@v4.14.1
with:
commit_message: "chore: Updated Lockfile"
commit_user_name: "cider-chore[bot]"
commit_user_email: "cider-chore[bot]@users.noreply.github.com"
prettier:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [ 18 ]
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v2
with:
# Make sure the actual branch is checked out when running on pull requests
ref: ${{ github.head_ref }}
- name: Prettify code
run: npm run prettier
- name: Commit Prettier Code
uses: stefanzweifel/git-auto-commit-action@v4.14.1
with:
commit_message: "chore: Prettified Code\n [ci skip]"
commit_user_name: "cider-chore[bot]"
commit_user_email: "cider-chore[bot]@users.noreply.github.com"

118
.github/workflows/cider-chores.yml vendored Normal file
View file

@ -0,0 +1,118 @@
name: Cider Chores
on:
push:
branches:
- main
jobs:
update-lockfile:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18]
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v3
with:
# Make sure the actual branch is checked out when running on pull requests
ref: ${{ github.head_ref }}
- uses: pnpm/action-setup@v2.2.2
with:
version: 7
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
- name: Update lockfile
run: pnpm i --lockfile-only
- name: Commit Updated Lockfile
uses: stefanzweifel/git-auto-commit-action@v4.14.1
with:
commit_message: "chore: Updated Lockfile"
commit_user_name: "cider-chore[bot]"
commit_user_email: "cider-chore[bot]@users.noreply.github.com"
prettier:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18]
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v3
with:
# Make sure the actual branch is checked out when running on pull requests
ref: ${{ github.head_ref }}
- name: Prettify code
run: npm run format:write
- name: Commit Prettier Code
uses: stefanzweifel/git-auto-commit-action@v4.14.1
with:
commit_message: "chore: Prettified Code\n [ci skip]"
commit_user_name: "cider-chore[bot]"
commit_user_email: "cider-chore[bot]@users.noreply.github.com"
update-i18n-source:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18]
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Update Source Files
run: cp src/i18n/en_US.json src/i18n/source/en_US.json
- name: Commit Updated Source File
uses: stefanzweifel/git-auto-commit-action@v4.14.1
with:
commit_message: "chore: Updated i18n Source\n [ci skip]"
commit_user_name: "cider-chore[bot]"
commit_user_email: "cider-chore[bot]@users.noreply.github.com"
synchronize-with-crowdin:
runs-on: ubuntu-latest
if: ${{ false }} # disable for now
steps:
- name: Checkout
uses: actions/checkout@v3
- name: crowdin action
uses: crowdin/github-action@1.4.13
with:
upload_translations: true
download_translations: false
project_id: ${{ secrets.CROWDIN_PROJECT_ID }}
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
source: '/src/i18n/source/**.*'
translation: '/src/i18n/%locale_with_underscore%.json'
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

37
.github/workflows/pr-chores.yml vendored Normal file
View file

@ -0,0 +1,37 @@
name: PR Chores
on: [pull_request]
jobs:
linter-check:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
node: [14]
steps:
- name: Checkout 🛎
uses: actions/checkout@v2
- name: Setup pnpm 🚧F
uses: pnpm/action-setup@v2.2.2
with:
version: 7
- name: Setup node env 🏗
uses: actions/setup-node@v3.4.1
with:
node-version: ${{ matrix.node }}
check-latest: true
cache: 'pnpm'
- name: Install dependencies 👨🏻‍💻
run: pnpm install
- name: Run linter 👀
uses: wearerequired/lint-action@v2
with:
prettier: true
prettier_args: "'**/*.{js,json,ts,css,vue,less}'"

View file

@ -19,4 +19,4 @@ jobs:
stale-issue-label: "stale"
stale-pr-label: "stale"
exempt-all-assignees: true
exempt-issue-labels: 'more-info,work-in-progress,accessibility-feature,help-wanted'
exempt-issue-labels: 'more-info,work-in-progress,accessibility-feature,help-wanted,persist'

1
.npmrc
View file

@ -3,4 +3,3 @@ public-hoist-pattern=*
shamefully-hoist=true
auto-install-peers=true
strict-peer-dependencies=false
store-dir=.pnpm-store

View file

@ -1,4 +1,5 @@
src/renderer/apple-hls*
src/renderer/*hls*.js
build/*
src/renderer/lib/*
*.min.*
.pnpm-store

View file

@ -1,2 +1,13 @@
bracketSameLine: true
printWidth: 240
{
"printWidth": 600,
"singleAttributePerLine": true,
"bracketSameLine": true,
"overrides": [
{
"files": "src/renderer/main/**/*.js",
"options": {
"singleAttributePerLine": false
}
}
]
}

View file

@ -4,7 +4,7 @@
<br><br>
<img src="https://img.shields.io/github/stars/ciderapp/Cider?label=Stars" alt="GitHub Stars"/>
<img src="https://img.shields.io/github/forks/ciderapp/Cider?label=Forks" alt="GitHub Forks"/>
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/cider-music"><img src="https://badges.crowdin.net/cider-music/localized.svg"></a>
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/cider-music"><img src="https://badges.crowdin.net/cider-i18n/localized.svg"></a>
<br>
<a target="_blank" href="https://ko-fi.com/cryptofyre"><img src="https://img.shields.io/badge/Buy%20Us%20a%20Coffee-donate-B48C69?logo=Ko-fi&logoColor=FFFFFF" alt="Buy Me A Coffee"/></a>
<a target="_blank" href="https://opencollective.com/ciderapp"><img src="https://img.shields.io/opencollective/all/ciderapp?color=%237FADF2&label=Backers%20and%20Sponsors&logo=opencollective" alt="Open Collective"/></a>

3
crowdin.yml Normal file
View file

@ -0,0 +1,3 @@
files:
- source: /src/i18n/source/en_US.json
translation: /src/i18n/%locale_with_underscore%.json

View file

@ -2,7 +2,7 @@
"name": "cider",
"applicationId": "Cider",
"productName": "Cider",
"version": "1.5.6",
"version": "1.5.7",
"description": "A new cross-platform Apple Music experience based on Electron and Vue.js written from scratch with performance in mind.",
"license": "AGPL-3.0",
"main": "./build/index.js",
@ -27,6 +27,7 @@
"pack": "electron-builder --dir",
"dist": "npm run build && electron-builder",
"dist:macarm": "npm run build && electron-builder --mac --arm64",
"dist:linuxdir": "npm run build && electron-builder --linux dir",
"slim-build:macarm": "ditto --arch arm64 ./dist/mac-universal/Cider.app ./dist/mac-universal/arm64/Cider.app",
"slim-build:macintel": "ditto --arch x86_64 ./dist/mac-universal/Cider.app ./dist/mac-universal/x86_x64/Cider.app",
"dist:universalNotWorking": "npm run build && electron-builder --mac --universal",
@ -36,11 +37,12 @@
"msft": "npm run build && electron-builder -c msft-package.json",
"mstest": "npm run build && electron-builder -c msft-test.json",
"postinstall": "electron-builder install-app-deps",
"prettier": "npx prettier --write '**/*.{js,json,ts,css,less}'"
"format:check": "npx prettier --check \"src/**/*.{js,json,ts,less}\"",
"format:write": "npx prettier --write \"src/**/*.{js,json,ts,less}\""
},
"dependencies": {
"@sentry/electron": "^4.0.0",
"@sentry/integrations": "^7.8.1",
"@sentry/electron": "^4.0.2",
"@sentry/integrations": "^7.13.0",
"adm-zip": "0.4.10",
"airtunes2": "git+https://github.com/ciderapp/node_airtunes2.git",
"castv2-client": "^1.2.0",
@ -56,7 +58,7 @@
"electron-window-state": "^5.0.3",
"express": "^4.18.1",
"get-port": "5.1.1",
"jimp": "^0.16.1",
"jimp": "^0.16.2",
"lastfmapi": "^0.1.1",
"mdns-js": "git+https://github.com/ciderapp/node-mdns-js.git",
"mpris-service": "^2.1.2",
@ -68,6 +70,7 @@
"run-script-os": "^1.1.6",
"source-map-support": "^0.5.21",
"ts-md5": "1.2.11",
"upnp-mediarenderer-client": "git+https://github.com/vapormusic/node-upnp-mediarenderer-client.git",
"v8-compile-cache": "^2.3.0",
"wallpaper": "5.0.1",
"ws": "^8.8.1",
@ -77,17 +80,18 @@
"devDependencies": {
"@types/adm-zip": "^0.5.0",
"@types/discord-rpc": "4.0.3",
"@types/express": "^4.17.13",
"@types/express": "^4.17.14",
"@types/node": "^18.7.18",
"@types/qrcode-terminal": "^0.12.0",
"@types/ws": "^8.5.3",
"@types/node": "^18.7.13",
"electron": "git+https://github.com/castlabs/electron-releases.git",
"electron-builder": "^23.0.2",
"electron-builder": "^23.3.3",
"electron-builder-notarize-pkg": "^1.2.0",
"electron-webpack": "^2.8.2",
"less": "^4.1.3",
"musickit-typescript": "^1.2.4",
"typescript": "^4.7.4",
"prettier": "2.7.1",
"typescript": "^4.8.3",
"vue-devtools": "^5.1.4",
"webpack": "~5.74.0"
},
@ -114,9 +118,9 @@
}
],
"build": {
"electronVersion": "20.1.0",
"electronVersion": "20.1.3",
"electronDownload": {
"version": "20.1.0+wvcus",
"version": "20.1.3+wvcus",
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
},
"appId": "cider",
@ -224,5 +228,8 @@
"NSUserNotificationAlertStyle": "alert"
}
}
},
"electronWebpack": {
"devtool": "source-map"
}
}

568
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -543,6 +543,7 @@
"settings.option.connectivity.lastfmScrobble.filterLoop.description": "Pwevent wooped twacks fwom being scwobbwed ow dispwayed in the Nyow Pwaying wist on Wast.fm.",
"settings.option.connectivity.lastfmScrobble.filterTypes": "Fiwtew Media Types (Wast.fm)",
"settings.option.connectivity.lastfmScrobble.manualToken": "Entew Wast.fm Token Manyuawwy",
"settings.option.connectivity.lastfmScrobble.manualToken.link": "Cwick hewe to get a Wast.fm token",
"settings.notyf.connectivity.lastfmScrobble.connectError": "Wast.fm Connyection Timed Out",
"settings.notyf.connectivity.lastfmScrobble.connectSuccess": "Wast.fm Connyection Successfuw",
"settings.notyf.connectivity.lastfmScrobble.connecting": "Connyecting to Wast.fm...",

View file

@ -10,6 +10,7 @@
"notification.updatingLibrarySongs": "Updating library songs...",
"notification.updatingLibraryAlbums": "Updating library albums...",
"notification.updatingLibraryArtists": "Updating library artists...",
"notification.buildingPlaylistCache": "Building Playlist Cache...",
"term.variables": "Variables",
"term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music",
@ -260,6 +261,7 @@
"action.removeFromQueue.success": "Removed from Queue",
"action.removeFromQueue.error": "Error Removing from Queue",
"action.createPlaylist": "Create a New Playlist",
"action.addToPlaylist.duplicate": "Item already exists in playlist. Do you want to continue?",
"action.addToPlaylist": "Add to Playlist",
"action.removeFromPlaylist": "Remove from Playlist",
"action.addToFavorites": "Add to Favorites",
@ -494,6 +496,7 @@
"settings.option.visual.theme.viewInfo": "View Info",
"settings.option.visual.theme.github.available": "Available",
"settings.option.visual.theme.github.applied": "Applied",
"settings.notyf.visual.theme.updateAvailable": "[Themes] {{ theme }} has an update available",
"settings.notyf.visual.theme.install.success": "Theme installed successfully",
"settings.notyf.visual.theme.install.error": "Theme installation failed",
"settings.header.visual.plugin": "Plugin",
@ -529,7 +532,13 @@
"settings.option.connectivity.discordRPC": "Discord Rich Presence",
"settings.option.connectivity.discordRPC.clientName": "Client Name",
"settings.option.connectivity.discordRPC.clearOnPause": "Clear Discord Rich Presence on Pause",
"settings.option.connectivity.discordRPC.hideButtons": "Hide buttons on Discord Rich Presence",
"settings.option.connectivity.discordRPC.showActivityButtons": "Show Activity Buttons",
"settings.option.connectivity.discordRPC.firstButton": "First Activity Button",
"settings.option.connectivity.discordRPC.secondButton": "Second Activity Button",
"settings.option.connectivity.discordRPC.buttons.listenOnCider": "Listen on Cider",
"settings.option.connectivity.discordRPC.buttons.viewOnAppleMusic": "View on Apple Music",
"settings.option.connectivity.discordRPC.buttons.viewOnOtherMusicServices": "View on Other Music Services",
"settings.option.connectivity.discordRPC.showSongLink": "Show Song.link button instead of Apple Music button on Discord Rich Presence",
"settings.option.connectivity.discordRPC.hideTimestamp": "Hide timestamp on Discord Rich Presence",
"settings.option.connectivity.discordRPC.detailsFormat": "Details Format",
"settings.option.connectivity.discordRPC.stateFormat": "State Format",
@ -542,6 +551,7 @@
"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.filterTypes.description": "Prevent tracks of the selected media types from being scrobbled or displayed in the Now Playing list on Last.fm.",
"settings.option.connectivity.lastfmScrobble.manualToken": "Enter Last.fm Token Manually",
"settings.option.connectivity.lastfmScrobble.manualToken.link": "Click here to get a Last.fm token",
"settings.notyf.connectivity.lastfmScrobble.connectError": "Last.fm Connection Timed Out",

View file

@ -2,7 +2,7 @@
"i18n.languageName": "Magyar",
"i18n.languageNameEnglish": "Hungarian",
"i18n.category": "main",
"i18n.authors": "@Amaru",
"i18n.authors": "@Amaru @BenjaminStonawski",
"app.name": "Cider",
"date.format": "${y} ${m} ${d}",
"dialog.cancel": "Mégsem",
@ -22,7 +22,6 @@
"term.logout": "Kijelentkezés",
"term.login": "Bejelentkezés",
"term.quickNav": "Főmenü",
"term.cast": "Tükrözés",
"term.about": "Névjegy",
"term.privateSession": "Privát hallgatás",
"term.disablePrivateSession": "Privát hallgatás kikapcsolása",
@ -32,6 +31,12 @@
"term.miniplayer": "Minilejátszó",
"term.history": "Előzmények",
"term.search": "Keresés",
"term.scroll": "Görgetési mód",
"term.scroll.infinite": "Végtelen",
"term.scroll.paged": "${songsPerPage} oldalanként",
"term.live": "ÉLŐ",
"term.showSearch": "Keresési mező megjelenítése",
"term.hideSearch": "Keresési mező elrejtése",
"term.library": "Könyvtár",
"term.listenNow": "Hallgatás most",
"term.browse": "Böngészés",
@ -41,8 +46,8 @@
"term.albums": "Albumok",
"term.artists": "Előadók",
"term.podcasts": "Podcastok",
"term.charts": "Listák",
"term.playlists": "Lejátszási listák",
"term.charts": "Listák",
"term.playlist": "Lejátszási lista",
"term.newPlaylist": "Új lejátszási lista",
"term.newPlaylistFolder": "Új mappa",
@ -52,10 +57,12 @@
"term.navigateBack": "Visszalépés",
"term.navigateForward": "Előrelépés",
"term.play": "Lejátszás",
"term.playpause": "Lejátszás/Megállítás",
"term.pause": "Megállítás",
"term.stop": "Leállítás",
"term.previous": "Előző",
"term.next": "Következő",
"term.skip": "Átugrás",
"term.shuffle": "Keverés",
"term.enableShuffle": "Keverés bekapcsolása",
"term.disableShuffle": "Keverés kikapcsolása",
@ -178,6 +185,7 @@
"term.top": "Top",
"term.version": "Verzió",
"term.noVideos": "Nincs találat",
"term.plugins": "Plug-in-ok",
"term.plugin": "Plug-in",
"term.pluginMenu": "Plug-in Menü",
"term.pluginMenu.none": "Nem találhatóak interaktív pluginok",
@ -194,6 +202,22 @@
"term.confirmLogout": "Biztosan ki szeretnél jelentkezni?",
"term.creditDesignedBy": "Készítette ${authorUsername}",
"term.discNumber": "${discNumber}. lemez",
"term.reload": "Biztosan újraindítod a Cider-t?",
"term.toggleprivate": "Privát zenehallgatás",
"term.webremote": "Távirányító",
"term.cast": "Tükrözés",
"term.cast2": "Tükrözés eszközökre",
"term.quit": "Kilépés",
"term.zoomin": "Nagyítás",
"term.zoomout": "Kicsinyítés",
"term.zoomreset": "Nagyítás visszaállítása",
"term.fullscreen": "Teljes képernyő",
"term.nowPlaying": "Jelenleg lejátszás alatt",
"home.syncFavorites": "Kedvencek szinkronizálása",
"home.syncFavorites.gettingArtists": "Kedvenc előadók betöltése..",
"action.favorite": "Kedvenc",
"action.removeFavorite": "Eltávolítás a kedvencek közül",
"action.refresh": "Frissítés",
"home.title": "Kezdőlap",
"home.recentlyPlayed": "Nemrég játszott",
"home.recentlyAdded": "Nemrég hozzáadott",
@ -222,6 +246,7 @@
"action.delete": "Törlés",
"action.edit": "Szerkesztés",
"action.done": "Kész",
"action.submit": "Befejezés",
"action.editTracklist": "Dalok szerkesztése",
"action.addToLibrary": "Hozzáadás a Könyvtárhoz",
"action.addToLibrary.success": "Hozzáadva a Könyvtárhoz",
@ -266,11 +291,7 @@
"action.export": "Exportálás",
"action.showAlbum": "Teljes album megjelenítése",
"action.tray.minimize": "Kicsinyítés a Tálcára",
"action.tray.quit": "Bezárás",
"action.tray.show": "Cider megjelenítése",
"action.tray.playpause": "Lejátszás/Megállítás",
"action.tray.next": "Következő",
"action.tray.previous": "Előző",
"action.tray.listento": "Ezt hallgatja:",
"action.update": "Frissítés",
"action.install": "Telepítés",
@ -290,44 +311,25 @@
"action.createNew": "Új létrehozása...",
"action.openArtworkInBrowser": "Borító megnyitása a böngészőben",
"action.scrollToTop": "Felülre",
"menubar.options.about": "Névjegy",
"menubar.options.settings": "Beállítások",
"menubar.options.quit": "Bezárás",
"menubar.options.view": "View ",
"menubar.options.reload": "Reload",
"menubar.options.forcereload": "Force Reload",
"menubar.options.view": "Jelenítse meg a(z) ",
"menubar.options.reload": "Újratöltés",
"menubar.options.forcereload": "Kényszerített újratöltés",
"menubar.options.toggledevtools": "Fejlesztői eszközök",
"menubar.options.window": "Ablak",
"menubar.options.minimize": "Kicsinyítés",
"menubar.options.toggleprivate": "Privát hallgatás ki/be",
"menubar.options.webremote": "Távirányító",
"menubar.options.audio": "Hangbeállítások",
"menubar.options.plugins": "Plug-in Menü",
"menubar.options.controls": "Controls",
"menubar.options.next": "Következő",
"menubar.options.playpause": "Lejátszás/Megállítás",
"menubar.options.previous": "Előző",
"menubar.options.controls": "Irányítások",
"menubar.options.volumeup": "Hangerő fel",
"menubar.options.volumedown": "Hangerő le",
"menubar.options.browse": "Böngészés",
"menubar.options.artists": "Előadók",
"menubar.options.search": "Keresés",
"menubar.options.albums": "Albumok",
"menubar.options.cast": "Tükrözés",
"menubar.options.account": "Fiók",
"menubar.options.accountsettings": "Fiókbeállítások",
"menubar.options.signout": "Kijelentkezés",
"menubar.options.support": "Támogatás",
"menubar.options.discord": "Discord",
"menubar.options.github": "GitHub Wiki",
"menubar.options.report": "Report a...",
"menubar.options.bug": "Bug",
"menubar.options.feature": "Feature Request",
"menubar.options.trans": "Translation Report/Request",
"menubar.options.report": "Jelentsd be egy...",
"menubar.options.bug": "Hiba",
"menubar.options.feature": "Funkciókérés",
"menubar.options.trans": "Fordítással kapcsolatos bejelentés/kérelem",
"menubar.options.license": "Licensz megtekintése",
"menubar.options.conf": "Open Configuration File in Editor",
"menubar.options.listennow": "Hallgatás most",
"menubar.options.recentlyAdded": "Nemrég hozzáadott",
"menubar.options.songs": "Dalok",
"settings.header.general": "Általános",
"settings.header.general.description": "A Cider általános beállításainak módosítása.",
@ -348,13 +350,19 @@
"settings.option.general.customizeSidebar": "Oldalsáv elemeinek testreszabása",
"settings.option.general.customizeSidebar.customize": "Testreszabás",
"settings.option.general.keybindings": "Billentyűparancsok",
"settings.option.general.keybindings.library": "Könyvtár",
"settings.option.general.keybindings.session": "Zenehallgatás",
"settings.option.general.keybindings.control": "Irányítások",
"settings.option.general.keybindings.interface": "Felület",
"settings.option.general.keybindings.advanced": "Haladó",
"settings.option.general.keybindings.pressCombination": "Nyomj le két billentyűt a parancs frissítéséhez.",
"settings.option.general.keybindings.pressEscape": "Nyomj Escape-et a visszalépéshez.",
"settings.notyf.general.keybindings.update.success": "Billentyűparancs sikeresen frissítve",
"settings.prompt.general.keybindings.update.success": "Billentyűparancs sikeresen frissítve. Kattints az OK-ra a Cider újraindításához!",
"settings.option.general.keybindings.open": "Megnyitás",
"settings.option.general.themeUpdateNotification": "Automatikus témafrissítések keresése",
"settings.option.general.showLovedTracksInline": "Show loved tracks inline",
"settings.option.general.pagination": "Elemek megjelenítése oldalanként",
"settings.options.general.pagination.description": "Ez határozza meg, hogy mennyi zeneszám/album jelenjen meg eleinte a végtelen görgetésben, vagy hány zeneszám/album jelenjen meg egyetlen oldalon",
"settings.description.search": "Keresés",
"settings.description.albums": "Albumkönyvtár",
"settings.description.artists": "Előadókönyvtár",
@ -410,6 +418,15 @@
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.description": "Az Atmoszféra Változató™ modul módját változtatja meg.",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.NATURAL_STANDARD": "Natural (Standard)",
"settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode.NATURAL_PLUS": "Natural (Plus)",
"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.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.ciderPPE": "Cider Adrenaline Processor™",
"settings.option.audio.enableAdvancedFunctionality.ciderPPE.description": "Feljavítja az AAC hang minőségét egy valós idejű algoritmus segítségével, ami kihasználja az emberi hallás pszichoakusztikus modelljeit és az AAC hang kódolási jellemzőit.",
"settings.warn.audio.enableAdvancedFunctionality.ciderPPE.compatibility": "A CAP nem kompatibilis a Térbeli Hanggal. Kapcsold ki a Térbeli Hangot a folytatáshoz.",
@ -426,23 +443,22 @@
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.description": "Előre beállított Térbeli hangzás, ami letiltja a Térbeli Hang testreszabási beállításait. A Térbeli Hangot be kell kapcsolni az engedélyezéshez.",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile": "Cider Térbeli Hangprofil",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.description": "Megváltoztatja a Térbeli Hang előbeállítás profilját.",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.standard": "Standard",
"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.diffused": "Diffused",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.bplk": "Encore",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.hw2k": "Expanded Encore",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.standard": "Alapértelmezett",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.soundstage": "Hangszínpad",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.separation": "Elválasztás",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.minimal": "Minimális",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.audiophile": "Audiofil",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.diffused": "Diffúz",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.bplk": "Ráadás",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.hw2k": "Kiterjesztett ráadás",
"settings.warn.audio.enableAdvancedFunctionality.audioSpatialization.compatibility": "A Térbeli Hang nem kompatibilis a CAP-pal. Kapcsold ki a CAP-ot a folytatáshoz.",
"settings.option.audio.dbspl.display": "dB SPL kijelzés",
"settings.option.audio.dbspl.description": "(Haladó felhasználóknak) A hangerő dB SPL-ben történő kijelzése a dBFS helyett.",
"settings.option.audio.dbfs.calibration": "0 dBFS kalibráció",
"settings.option.audio.dbfs.description": "Enter the peak Z-weighted dB SPL when Cider is at 0 dBFS.",
"settings.option.audio.dbfs.description": "Adja meg a csúcs Z-súlyozott dB SPL értékét, amikor a Cider 0 dBFS-en van.",
"settings.header.visual": "Vizuális",
"settings.header.visual.description": "A Cider vizuális beállításainak módosítása.",
"settings.option.visual.windowStyle": "Ablakelrendezés",
"settings.option.visual.purplePodcastPlaybackBar": "Lila lejátszási sáv a Podcastoknál",
"settings.option.visual.windowBackgroundStyle": "Ablakháttér stílusa",
"settings.header.visual.windowBackgroundStyle.none": "Sehol",
"settings.header.visual.windowBackgroundStyle.artwork": "Borító",
@ -465,12 +481,14 @@
"settings.option.visual.uiscale": "Nagyítás mértéke",
"settings.header.visual.theme": "Téma",
"settings.option.visual.theme.github.download": "Telepítés GitHub URL-ről",
"settings.option.visual.theme.github.openfolder": "Témák mappa megnyitása",
"settings.option.visual.theme.github.explore": "Témák felfedezése",
"settings.header.visual.theme.github.page": "Témák a GitHub-ról",
"settings.option.visual.theme.github.install.confirm": "Biztosan szeretnéd telepíteni a(z) {{ repo }} témát?",
"settings.prompt.visual.theme.github.URL": "Add meg a telepítendő téma URL-jét",
"settings.prompt.visual.theme.uninstallTheme": "Biztos, hogy törölni szeretnéd a {{ theme }} témát?",
"settings.option.visual.theme.checkForUpdates": "Frissítések keresése",
"settings.header.visual.styles": "Stílusok",
"settings.option.visual.theme.manageStyles": "Témakezelő",
"settings.option.visual.theme.uninstall": "Törlés",
"settings.option.visual.theme.viewInfo": "Információk",
@ -522,6 +540,13 @@
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Last.fm Now Playing engedélyezése",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Közreműködő zenészek eltávolítása a címből (Last.fm)",
"settings.option.connectivity.lastfmScrobble.filterLoop": "Ismételt zeneszám szűrése (Last.fm)",
"settings.option.connectivity.lastfmScrobble.filterLoop.description": "Ismételt zeneszámok scrobble-zésének és megjelenítésének letiltása a jelenleg lejátszás alatt listában (Last.fm)",
"settings.option.connectivity.lastfmScrobble.filterTypes": "Médiatípusok szűrése (Last.fm)",
"settings.option.connectivity.lastfmScrobble.manualToken": "Last.fm Token beírása manuálisan",
"settings.option.connectivity.lastfmScrobble.manualToken.link": "Last.fm Token legenerálásához kattints ide",
"settings.notyf.connectivity.lastfmScrobble.connectError": "A Last.fm csatlakozása során időtullépés lépett fel",
"settings.notyf.connectivity.lastfmScrobble.connectSuccess": "A Last.fm csatlakozása sikeres volt",
"settings.notyf.connectivity.lastfmScrobble.connecting": "Last.fm-hez való csatlakozás...",
"settings.header.debug": "Hibaelhárítás",
"settings.option.debug.copy_log": "Napló másolása a vágólapra",
"settings.option.debug.openAppData": "Cider mappa megnyitása",
@ -533,17 +558,24 @@
"settings.option.experimental.unknownPlugin.description": "Engedélyezi azon pluginok telepítését is, amik nem tartoznak a Cider Plugin Gyűjteménybe, ezáltal lehetséges biztonsági kockázatot jelenthetnek.",
"settings.option.experimental.compactUI": "Kompakt felület",
"settings.option.window.close_button_hide": "A bezárás gomb rejtse el az alkalmazást",
"settings.option.window.maxElementScale": "Maximális elem skála",
"settings.option.experimental.inline_playlists": "Ablakon belüli lejátszási listák és albumok",
"settings.option.advanced.playlistTrackMapping": "Lejátszási lista feltérképezés",
"settings.option.advanced.playlistTrackMapping.description": "Engedélyezi a lejátszási listák mély feltérképezését, ami meg tudja határozni, hogy egy zeneszám mely lejátszási listákba található. A lejátszási lista gyorsítótár felépítésének időtartalma nagy mértékben megnőhet.",
"settings.option.visual.transparent": "Átlátszó keret",
"settings.option.visual.transparent.description": "a Témák támogatása és újraindítás szükséges",
"settings.option.visual.transparent.description": "a Témák támogatása (újraindítás szükséges)",
"settings.option.visual.customAccentColor": "Egyedi kiemelőszín",
"settings.option.visual.accentColor": "Kiemelőszín",
"settings.option.visual.purplePodcastPlaybackBar": "Lila lejátszási sáv podcastok lejátszása esetén",
"settings.option.visual.compactArtistHeader": "Kompakt előadó fejléc",
"settings.option.visual.windowColor": "Az ablak színárnyalata",
"settings.header.visual.windowBackgroundStyle.color": "Színárnyalat",
"settings.header.advanced": "Haladó",
"settings.header.connect": "Sync",
"settings.option.connect.link_account": "Enable Sync with Cider Connect",
"settings.option.connect.link_account.description": "Linking your Discord account with Cider Connect allows you to store userdata including Settings, EQ's, and eventually more once finished. (Work In Progress)",
"settings.header.connect": "Szinkronizálás",
"settings.option.connect.link_account": "Szinkronizálás a Cider Connect szolgáltatással engedélyezése",
"settings.option.connect.link_account.description": "A Discord fiók összekapcsolása a Cider Connect szolgáltatással lehetővé teszi a felhasználói adatok tárolását, beleértve a beállításokat, az EQ-kat és esetleg még többet. (Fejlesztés alatt)",
"spatial.notTurnedOn": "A Térbeli Hang ki van kapcsolva, használatához kapcsolja be.",
"spatial.spatialProperties": "Térbeli Hang",
"spatial.spatialProperties": "Térbeli hang",
"spatial.width": "Szélesség",
"spatial.height": "Magasság",
"spatial.depth": "Hosszúság",
@ -574,5 +606,32 @@
"share.platform.email": "Email",
"share.platform.songLink": "song.link URL másolása",
"share.platform.clipboard": "Link másolása",
"about.thanks": "Köszönet a Cider Collective csapatának és minden hozzájárulónak."
"about.thanks": "Köszönet a Cider Collective csapatának és minden hozzájárulónak.",
"oobe.yes": "Igen",
"oobe.no": "Nem",
"oobe.next": "Következő",
"oobe.previous": "Előző",
"oobe.done": "Kész",
"oobe.amupsell.title": "Még mielőtt belevágnánk",
"oobe.amupsell.text": "A Cider Apple Music előfizetést igényel\nA Cider nem támogatja az Apple Music Voice Plan-t vagy más fajta promóciós próba előfizetéseket. Ha már Apple Music előfizető vagy, lépj tovább.",
"oobe.amupsell.subscribeBtn": "Előfizetés Apple Music-ra",
"oobe.amupsell.explainBtn": "Magyarázat",
"oobe.amupsell.subscribeUrl": "https://apple.co/3MdqJVQ",
"oobe.amupsell.amWebUrl": "https://beta.music.apple.com/",
"oobe.amupsell.promoExplained": "Egyes promóciós és nem egyesült államokbeli Apple Music próba-előfizetések nem férnek hozzá a szükséges Apple Music Web Player API-khoz, amelyek a Cider működéséhez szükségesek. Ha le szeretnéd ellenőrizni, hogy az aktív előfizetésed kompatibilis a Cider-rel, kattints a <a href='{{ amWebUrl }}'>{{ amWebUrl }}</a> linkre, jelentkezz be és próbálj meg lejátszani bármilyen zenét. Ha működik, akkor jó hírunk van! Készen állsz arra, hogy használd a Cider-t. Viszont ha mégsem működött, fizess elő Apple Music-ra ezen a linken: <a href='{{ subscribeUrl }}'>{{ subscribeUrl }}</a>",
"oobe.intro.title": "Üdvözlünk a Cider-ben!",
"oobe.intro.subtitle": "",
"oobe.intro.text": "Állítsunk be néhány dolgot, hogy tetszés szerint használhasd a Cider-t. Ezeket a beállításokat később bármikor módosíthatod.",
"oobe.general.title": "Általános",
"oobe.general.subtitle": "",
"oobe.general.text": "",
"oobe.audio.title": "Hang",
"oobe.audio.subtitle": "",
"oobe.audio.text": "A Cider egyedi hangolású és tervezett hangkészletekkel rendelkezik, amely gazdag, kiváló minőségű hangélményt biztosít.\nKöztük van a Cider Adrenaline, Atmosphere Realizer, és a Spatialized Audio.\nA hangkészletek engedélyezéséhez a \"Fejlett audiófunkciók\"-at be kell kapcsolni.\nA fejlett audiófunkciók engedélyezése hozzáférést biztosít ezekhez a hangkészletekhez a Cider Audio Labs szolgáltatásban, amely az alkalmazás beállításai között található.",
"oobe.audio.advancedFunctionality": "",
"oobe.visual.title": "Vizuális",
"oobe.visual.subtitle": "",
"oobe.visual.text": "",
"oobe.visual.layout.text": "A Cider két különböző ablakmegjelenéssel rendelkezik.\nA Maverick egy iTunes-ra hasonló megjelenést biztosít, a zenelejátszó az ablak tetején található.\nA Mojave egy új hullám a Cider Collective által készítve.\n\nBármikor megváltoztathatod a megjelenést a beállításokban.",
"oobe.amsignin.title": ""
}

View file

@ -132,8 +132,8 @@
"term.reset": "Resetten",
"term.tracks": "nummers",
"term.track": {
"one" : "nummer",
"other" : "nummers"
"one": "nummer",
"other": "nummers"
},
"term.videos": "Video's",
"term.menu": "Menu",

View file

@ -10,6 +10,7 @@
"notification.updatingLibrarySongs": "Updating library songs...",
"notification.updatingLibraryAlbums": "Updating library albums...",
"notification.updatingLibraryArtists": "Updating library artists...",
"notification.buildingPlaylistCache": "Building Playlist Cache...",
"term.variables": "Variables",
"term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music",
@ -260,6 +261,7 @@
"action.removeFromQueue.success": "Removed from Queue",
"action.removeFromQueue.error": "Error Removing from Queue",
"action.createPlaylist": "Create a New Playlist",
"action.addToPlaylist.duplicate": "Item already exists in playlist. Do you want to continue?",
"action.addToPlaylist": "Add to Playlist",
"action.removeFromPlaylist": "Remove from Playlist",
"action.addToFavorites": "Add to Favorites",
@ -494,6 +496,7 @@
"settings.option.visual.theme.viewInfo": "View Info",
"settings.option.visual.theme.github.available": "Available",
"settings.option.visual.theme.github.applied": "Applied",
"settings.notyf.visual.theme.updateAvailable": "[Themes] {{ theme }} has an update available",
"settings.notyf.visual.theme.install.success": "Theme installed successfully",
"settings.notyf.visual.theme.install.error": "Theme installation failed",
"settings.header.visual.plugin": "Plugin",
@ -529,7 +532,13 @@
"settings.option.connectivity.discordRPC": "Discord Rich Presence",
"settings.option.connectivity.discordRPC.clientName": "Client Name",
"settings.option.connectivity.discordRPC.clearOnPause": "Clear Discord Rich Presence on Pause",
"settings.option.connectivity.discordRPC.hideButtons": "Hide buttons on Discord Rich Presence",
"settings.option.connectivity.discordRPC.showActivityButtons": "Show Activity Buttons",
"settings.option.connectivity.discordRPC.firstButton": "First Activity Button",
"settings.option.connectivity.discordRPC.secondButton": "Second Activity Button",
"settings.option.connectivity.discordRPC.buttons.listenOnCider": "Listen on Cider",
"settings.option.connectivity.discordRPC.buttons.viewOnAppleMusic": "View on Apple Music",
"settings.option.connectivity.discordRPC.buttons.viewOnOtherMusicServices": "View on Other Music Services",
"settings.option.connectivity.discordRPC.showSongLink": "Show Song.link button instead of Apple Music button on Discord Rich Presence",
"settings.option.connectivity.discordRPC.hideTimestamp": "Hide timestamp on Discord Rich Presence",
"settings.option.connectivity.discordRPC.detailsFormat": "Details Format",
"settings.option.connectivity.discordRPC.stateFormat": "State Format",
@ -542,6 +551,7 @@
"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.filterTypes.description": "Prevent tracks of the selected media types from being scrobbled or displayed in the Now Playing list on Last.fm.",
"settings.option.connectivity.lastfmScrobble.manualToken": "Enter Last.fm Token Manually",
"settings.option.connectivity.lastfmScrobble.manualToken.link": "Click here to get a Last.fm token",
"settings.notyf.connectivity.lastfmScrobble.connectError": "Last.fm Connection Timed Out",

View file

@ -7,10 +7,10 @@
"date.format": "${y}年${m}月${d}日",
"dialog.cancel": "取消",
"dialog.ok": "好",
"notification.updatingLibrarySongs": "正在更新歌曲资料库...",
"notification.updatingLibraryAlbums": "正在更新专辑资料库...",
"notification.updatingLibraryArtists": "正在更新艺人资料库...",
"term.variables": "Variables",
"notification.updatingLibrarySongs": "正在更新歌曲资料库……",
"notification.updatingLibraryAlbums": "正在更新专辑资料库……",
"notification.updatingLibraryArtists": "正在更新艺人资料库……",
"term.variables": "变量",
"term.appleInc": "Apple Inc.",
"term.appleMusic": "Apple Music",
"term.applePodcasts": "Apple Podcasts",
@ -131,7 +131,7 @@
"term.amLive": "Apple Music Live",
"term.language": "语言",
"term.funLanguages": "恶搞",
"term.noLyrics": "加载中... / 无搜索结果 / 纯音乐",
"term.noLyrics": "纯音乐 / 无歌词",
"term.copyright": "版权所有",
"term.rightsReserved": "保留所有权利。",
"term.sponsor": "赞助",
@ -159,7 +159,7 @@
"term.defaultPresets": "默认预设",
"term.userPresets": "用户预设",
"term.requestError": "请求出现一个问题。",
"term.song.link.generate": "获取 song.link 共享链接...",
"term.song.link.generate": "获取 song.link 共享链接……",
"term.musicVideos": "音乐视频",
"term.stations": "电台",
"term.curators": "策展人",
@ -199,7 +199,7 @@
"term.fullscreen": "全屏模式",
"term.nowPlaying": "正在播放",
"home.syncFavorites": "同步喜爱艺人",
"home.syncFavorites.gettingArtists": "获取喜爱艺人...",
"home.syncFavorites.gettingArtists": "获取喜爱艺人……",
"action.favorite": "喜爱",
"action.removeFavorite": "取消喜爱",
"action.refresh": "刷新",
@ -212,7 +212,7 @@
"home.friendsListeningTo": "朋友正在听",
"home.followedArtists": "关注的艺人",
"error.appleMusicSubRequired": "需要订阅 Apple Music 以使用 Cider",
"error.connectionError": "无法连接 Apple Music。",
"error.connectionError": "无法连接 Apple Music。",
"error.noResults": "没有结果。",
"error.noResults.description": "尝试更改搜索条件。",
"podcast.followOnCider": "在 Cider 中关注",
@ -281,7 +281,7 @@
"action.update": "更新",
"action.install": "安装",
"action.copy": "拷贝",
"action.newpreset": "新建预设...",
"action.newpreset": "新建预设……",
"action.deletepreset": "删除预设",
"action.open": "打开",
"action.close": "关闭",
@ -292,8 +292,8 @@
"action.cast.airplay": "隔空播放",
"action.cast.airplay.underdevelopment": "隔空播放仍处于开发阶段中,敬请期待。",
"action.cast.scan": "搜索",
"action.cast.scanning": "搜索中...",
"action.createNew": "添加...",
"action.cast.scanning": "搜索中……",
"action.createNew": "添加……",
"action.openArtworkInBrowser": "在浏览器中打开专辑封面",
"action.scrollToTop": "回到顶部",
"menubar.options.view": "查看",
@ -309,12 +309,12 @@
"menubar.options.account": "账户",
"menubar.options.signout": "注销",
"menubar.options.support": "支持",
"menubar.options.report": "报告...",
"menubar.options.report": "报告……",
"menubar.options.bug": "Bug",
"menubar.options.feature": "功能请求",
"menubar.options.trans": "翻译报告/请求",
"menubar.options.license": "查看授权",
"menubar.options.conf": "在编辑器打开配置文件",
"menubar.options.conf": "在编辑器打开配置文件",
"menubar.options.zoom": "缩放",
"settings.header.general": "通用",
"settings.header.general.description": "调整 Cider 的通用设置",
@ -325,14 +325,14 @@
"settings.option.general.resumebehavior.locally.description": "Cider 将还原您在这台电脑上的最后一次操作。",
"settings.option.general.resumebehavior.history": "历史",
"settings.option.general.resumebehavior.history.description": "Cider 将跨设备将您的整个 Apple Music 历史记录中的最后一首歌曲排队入列。",
"settings.option.general.resumetabs": "启动时打开的选项页面",
"settings.option.general.resumetabs.description": "您可以选择启动 Cider 时默认打开的页面。",
"settings.option.general.resumetabs": "启动时打开的页面",
"settings.option.general.resumetabs.description": "您可以选择启动 Cider 时默认打开的页面。",
"settings.option.general.resumetabs.dynamic": "动态",
"settings.option.general.resumetabs.dynamic.description": "Cider 将自动打开您上次停留的页面。",
"settings.option.general.language.main": "语言",
"settings.option.general.language.fun": "恶搞语言",
"settings.option.general.language.unsorted": "未分类",
"settings.option.general.customizeSidebar": "自定义侧边栏的功能",
"settings.option.general.customizeSidebar": "自定义侧边栏项目",
"settings.option.general.customizeSidebar.customize": "自定义",
"settings.option.general.keybindings": "快捷操作键",
"settings.option.general.keybindings.library": "资料库",
@ -356,7 +356,7 @@
"settings.description.remote": "远程控制",
"settings.description.audio": "音频设定",
"settings.description.plugins": "插件目录",
"settings.description.cast": "投射到装置",
"settings.description.cast": "投射到设备",
"settings.description.settings": "设置",
"settings.description.developer": "开发者工具",
"settings.description.listnow": "现在就听",
@ -384,7 +384,7 @@
"settings.header.audio.quality.standard.description": "64 kbps",
"settings.option.audio.seamlessTransition": "无缝播放",
"settings.option.audio.enableAdvancedFunctionality": "启用高级音频功能",
"settings.option.audio.enableAdvancedFunctionality.description": "打开 AudioContext 将启用类似音量平衡和等化器的高级设置。但这并不一定适合每部电脑,可能会发生音乐卡顿。",
"settings.option.audio.enableAdvancedFunctionality.description": "打开高级音频功能将启用类似音量平衡和等化器的高级设置。但这并不一定适合每部电脑,可能会发生音乐卡顿。",
"settings.warn.audio.enableAdvancedFunctionality.lowcores": "您的电脑可能无法处理这些功能, 您确定要继续?",
"settings.option.audio.audioLab": "Cider 音频实验室",
"settings.option.audio.audioLab.description": "包含由 Cider 开发团队进行的各种音频优化功能。",
@ -434,11 +434,11 @@
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.minimal": "微调",
"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.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.bplk": "安可舞台",
"settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.hw2k": "大型安可舞台",
"settings.warn.audio.enableAdvancedFunctionality.audioSpatialization.compatibility": "空间音频并不兼容数码增强音频处理器。请关闭数码增强音频处理器后再继续进行操作。",
"settings.option.audio.dbspl.display": "显示 dB SPL(声压)",
"settings.option.audio.dbspl.description": "(专业用户选项) 音量滑动条显示 dB SPL 而非 dBFS。",
"settings.option.audio.dbspl.display": "显示 dB SPL(声压)",
"settings.option.audio.dbspl.description": "(专业用户选项)音量滑动条显示 dB SPL 而非 dBFS。",
"settings.option.audio.dbfs.calibration": "0 dBFS 校正",
"settings.option.audio.dbfs.description": "输入当 Cider 为 0 dBFS 时的峰值 Z 加权 dB SPL。",
"settings.header.visual": "外观",
@ -504,7 +504,7 @@
"settings.header.lyrics": "歌词",
"settings.header.lyrics.description": "调整 Cider 的歌词设置",
"settings.option.lyrics.enableMusixmatch": "启用 Musixmatch 歌词",
"settings.option.lyrics.enableMusixmatchKaraoke": "启用卡拉 OK 模式(仅 Musixmatch",
"settings.option.lyrics.enableMusixmatchKaraoke": "启用卡拉OK模式 Musixmatch",
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌词语言偏好",
"settings.option.lyrics.enableYoutubeLyrics": "播放 MV 时使用 YouTube 歌词",
"settings.option.lyrics.enableQQLyrics": "启用 QQ 音乐的歌词",
@ -514,23 +514,30 @@
"settings.option.connectivity.discordRPC": "Discord 动态",
"settings.option.connectivity.discordRPC.clientName": "应用程序名称",
"settings.option.connectivity.discordRPC.clearOnPause": "暂停时清除 Discord 动态",
"settings.option.connectivity.discordRPC.hideButtons": "隐藏 Discord 动态上的按钮",
"settings.option.connectivity.discordRPC.showActivityButtons": "显示动态按钮",
"settings.option.connectivity.discordRPC.firstButton": "第一动态按钮",
"settings.option.connectivity.discordRPC.secondButton": "第二动态按钮",
"settings.option.connectivity.discordRPC.buttons.listenOnCider": "在 Cider 中聆听",
"settings.option.connectivity.discordRPC.buttons.viewOnAppleMusic": "在 Apple Music 中查看",
"settings.option.connectivity.discordRPC.buttons.viewOnOtherMusicServices": "在其他音乐服务中查看",
"settings.option.connectivity.discordRPC.showSongLink": "在 Discord 动态中显示 Song.link 按钮而非 Apple Music 按钮",
"settings.option.connectivity.discordRPC.hideTimestamp": "隐藏 Discord 动态上的时间戳",
"settings.option.connectivity.discordRPC.detailsFormat": "详细信息格式",
"settings.option.connectivity.discordRPC.stateFormat": "动态格式",
"settings.option.connectivity.discordRPC.reload": "重新加载 DiscordRPC",
"settings.option.connectivity.discordRPC.reconnectedToUser": "DiscordRPC 重新连接至用户:{{user}} ({{userid}})",
"settings.option.connectivity.lastfmScrobble": "Last.FM 音乐记录",
"settings.option.connectivity.lastfmScrobble.delay": "Last.FM 歌曲追踪延迟 (%)",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "打开 Last.FM 正在聆听",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "从歌名里去除合作者 (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.discordRPC.reload": "重新加载 Discord 动态",
"settings.option.connectivity.discordRPC.reconnectedToUser": "Discord 动态已重新连接至用户:{{user}} ({{userid}})",
"settings.option.connectivity.lastfmScrobble": "Last.fm 音乐记录",
"settings.option.connectivity.lastfmScrobble.delay": "Last.fm 歌曲追踪延迟(%",
"settings.option.connectivity.lastfmScrobble.nowPlaying": "打开 Last.fm 正在聆听",
"settings.option.connectivity.lastfmScrobble.removeFeatured": "从歌名里去除合作者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.option.connectivity.lastfmScrobble.manualToken.link": "点击此处以获取 Last.fm 验证码",
"settings.notyf.connectivity.lastfmScrobble.connectError": "Last.fm 连接超时",
"settings.notyf.connectivity.lastfmScrobble.connectSuccess": "Last.fm 连接成功",
"settings.notyf.connectivity.lastfmScrobble.connecting": "正在连接至 Last.fm...",
"settings.notyf.connectivity.lastfmScrobble.connecting": "正在连接至 Last.fm……",
"settings.header.debug": "调试",
"settings.option.debug.copy_log": "拷贝日志至剪贴板",
"settings.option.debug.openAppData": "打开 Cider 程序文件夹",

View file

@ -78,11 +78,15 @@ export class AppEvents {
case "webgpu":
console.info("WebGPU is enabled.");
app.commandLine.appendSwitch("enable-unsafe-webgpu");
if (process.platform === "linux") {
app.commandLine.appendSwitch("enable-features", "Vulkan");
}
break;
case "disabled":
console.info("Hardware acceleration is disabled.");
app.commandLine.appendSwitch("disable-gpu");
app.disableHardwareAcceleration();
break;
}
@ -232,7 +236,7 @@ export class AppEvents {
startArgs.forEach((arg) => {
console.log(arg);
if (arg.includes("cider://")) {
if (arg.includes("cider://") || arg.includes("itms://") || arg.includes("itmss://") || arg.includes("music://") || arg.includes("musics://")) {
console.debug("[InstanceHandler] (second-instance) Link detected with " + arg);
this.LinkHandler(arg);
} else if (arg.includes("--force-quit")) {

View file

@ -1,5 +1,5 @@
import { join } from "path";
import { app, BrowserWindow as bw, ipcMain, ShareMenu, shell, screen, dialog } from "electron";
import { app, BrowserWindow as bw, ipcMain, ShareMenu, shell, screen, dialog, nativeTheme } from "electron";
import * as windowStateKeeper from "electron-window-state";
import * as express from "express";
import * as getPort from "get-port";
@ -90,6 +90,7 @@ export class BrowserWindow {
"components/equalizer",
"components/add-to-playlist",
"components/queue",
"components/smarthints",
"components/mediaitem-scroller-horizontal",
"components/mediaitem-scroller-horizontal-large",
"components/mediaitem-scroller-horizontal-sp",
@ -441,6 +442,8 @@ export class BrowserWindow {
break;
}
nativeTheme.themeSource = utils.getStoreValue("visual.overrideDisplayTheme");
// Start the webserver for the browser window to load
// LocalFiles.DB.init()
this.startWebServer();
@ -476,7 +479,7 @@ export class BrowserWindow {
}
}
for (let i = 0; i < expectedFiles.length; i++) {
const file = join(utils.getPath("ciderCache"), expectedFiles[i]);
const file = join(join(app.getPath("userData"), "CiderCache"), expectedFiles[i]);
if (!existsSync(file)) {
writeFileSync(file, JSON.stringify([]));
}
@ -555,15 +558,11 @@ export class BrowserWindow {
res.send("Stopped");
break;
case "next":
BrowserWindow.win.webContents.executeJavaScript(
"if (MusicKit.getInstance().queue.nextPlayableItemIndex != -1 && MusicKit.getInstance().queue.nextPlayableItemIndex != null) {MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.nextPlayableItemIndex);}"
);
BrowserWindow.win.webContents.executeJavaScript("if (MusicKit.getInstance().queue.nextPlayableItemIndex != -1 && MusicKit.getInstance().queue.nextPlayableItemIndex != null) {MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.nextPlayableItemIndex);}");
res.send("Next");
break;
case "previous":
BrowserWindow.win.webContents.executeJavaScript(
"if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null) {MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex);}"
);
BrowserWindow.win.webContents.executeJavaScript("if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null) {MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex);}");
res.send("Previous");
break;
default: {
@ -619,6 +618,8 @@ export class BrowserWindow {
const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress;
if (!this.chromecastIP.includes(ip)) {
this.headerSent = false;
this.audioStream._readableState.buffer.clear();
this.audioStream._readableState.length = 0;
this.chromecastIP.push(ip);
}
req.socket.setTimeout(Number.MAX_SAFE_INTEGER);
@ -730,18 +731,11 @@ export class BrowserWindow {
details.requestHeaders["user-agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Cider/1.0.0 Chrome/96.0.4664.45 Electron/16.0.0 Safari/537.36";
}
if (details.url.includes("https://qq.com")) {
(details.requestHeaders["Accept"] = "*/*"),
(details.requestHeaders["Accept-Encoding"] = "gzip, deflate, br"),
(details.requestHeaders["Accept-Language"] = "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"),
(details.requestHeaders["Referer"] = "https://y.qq.com/"),
(details.requestHeaders["User-Agent"] = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X; zh-CN) AppleWebKit/537.51.1 (");
(details.requestHeaders["Accept"] = "*/*"), (details.requestHeaders["Accept-Encoding"] = "gzip, deflate, br"), (details.requestHeaders["Accept-Language"] = "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"), (details.requestHeaders["Referer"] = "https://y.qq.com/"), (details.requestHeaders["User-Agent"] = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X; zh-CN) AppleWebKit/537.51.1 (");
("KHTML, like Gecko) Mobile/17D50 UCBrowser/12.8.2.1268 Mobile AliApp(TUnionSDK/0.1.20.3) ");
}
if (details.url.includes("https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg")) {
(details.requestHeaders["Accept"] = "*/*"),
(details.requestHeaders["Accept-Encoding"] = "gzip, deflate, br"),
(details.requestHeaders["Accept-Language"] = "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"),
(details.requestHeaders["User-Agent"] = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X; zh-CN) AppleWebKit/537.51.1 (");
(details.requestHeaders["Accept"] = "*/*"), (details.requestHeaders["Accept-Encoding"] = "gzip, deflate, br"), (details.requestHeaders["Accept-Language"] = "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"), (details.requestHeaders["User-Agent"] = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X; zh-CN) AppleWebKit/537.51.1 (");
("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";
}
@ -1073,13 +1067,13 @@ export class BrowserWindow {
});
ipcMain.handle("put-cache", (_event, arg) => {
writeFileSync(join(utils.getPath("ciderCache"), `${arg.file}.json`), arg.data);
writeFileSync(join(join(app.getPath("userData"), "CiderCache"), `${arg.file}.json`), arg.data);
});
ipcMain.on("get-cache", (event, arg) => {
let read = "";
if (existsSync(join(utils.getPath("ciderCache"), `${arg}.json`))) {
read = readFileSync(join(utils.getPath("ciderCache"), `${arg}.json`), "utf8");
if (existsSync(join(join(app.getPath("userData"), "CiderCache"), `${arg}.json`))) {
read = readFileSync(join(join(app.getPath("userData"), "CiderCache"), `${arg}.json`), "utf8");
}
event.returnValue = read;
});
@ -1135,6 +1129,11 @@ export class BrowserWindow {
BrowserWindow.win.setBounds({ x, y });
});
// Override light, dark
ipcMain.on("changeDisplayTheme", (event, theme) => {
nativeTheme.themeSource = theme;
});
//Fullscreen
ipcMain.on("setFullScreen", (_event, flag) => {
BrowserWindow.win.setFullScreen(flag);
@ -1351,7 +1350,7 @@ export class BrowserWindow {
.then(async (buffer) => {
const metadata = await mm.parseBuffer(buffer, "audio/x-m4a");
let SoundCheckTag = metadata.native.iTunes[1].value;
console.log("sc", SoundCheckTag);
console.debug("sc", SoundCheckTag);
BrowserWindow.win.webContents.send("SoundCheckTag", SoundCheckTag);
})
.catch((err) => {
@ -1476,7 +1475,7 @@ export class BrowserWindow {
// Set window Handler
BrowserWindow.win.webContents.setWindowOpenHandler((x: any) => {
if (x.url.includes("apple") || x.url.includes("localhost")) {
if (x.url.includes("apple.com") || x.url.includes("localhost")) {
return { action: "allow" };
}
shell.openExternal(x.url).catch(console.error);
@ -1498,11 +1497,7 @@ export class BrowserWindow {
if (details.family === "IPv4" && !details.internal) {
if (!/(loopback|vmware|internal|hamachi|vboxnet|virtualbox)/gi.test(dev + (alias ? ":" + alias : ""))) {
if (details.address.substring(0, 8) === "192.168." || details.address.substring(0, 7) === "172.16." || details.address.substring(0, 3) === "10.") {
if (
!ip.startsWith("192.168.") ||
(String(ip2).startsWith("192.168.") && !ip.startsWith("192.168.") && String(ip2).startsWith("172.16.") && !ip.startsWith("192.168.") && !ip.startsWith("172.16.")) ||
(String(ip2).startsWith("10.") && !ip.startsWith("192.168.") && !ip.startsWith("172.16.") && !ip.startsWith("10."))
) {
if (!ip.startsWith("192.168.") || (String(ip2).startsWith("192.168.") && !ip.startsWith("192.168.") && String(ip2).startsWith("172.16.") && !ip.startsWith("192.168.") && !ip.startsWith("172.16.")) || (String(ip2).startsWith("10.") && !ip.startsWith("192.168.") && !ip.startsWith("172.16.") && !ip.startsWith("10."))) {
ip = details.address;
}
++alias;

View file

@ -88,6 +88,7 @@ export class Plugins {
win: utils.getWindow(),
dir: pluginPath,
dirName: file,
express: utils.getExpress(),
};
plugins[plugin.name] = new plugin(pluginEnv);
}

View file

@ -72,11 +72,18 @@ export class Store {
discord_rpc: {
enabled: true,
client: "Cider",
clear_on_pause: true,
hide_buttons: false,
hide_timestamp: false,
activity: {
state_format: "by {artist}",
details_format: "{title}",
hide_timestamp: false,
buttons: {
enabled: true,
first: "listenOnCider",
second: "viewOnAppleMusic",
options: ["listenOnCider", "viewOnAppleMusic", "viewOnOtherMusicServices"],
},
},
clear_on_pause: true,
},
lastfm: {
enabled: false,
@ -200,6 +207,7 @@ export class Store {
accentColor: "#fc3c44",
purplePodcastPlaybackBar: false,
maxElementScale: -1, // -1 default, anything else is a custom scale
overrideDisplayTheme: "system", // system , dark, light
},
lyrics: {
enable_mxm: true,
@ -232,7 +240,6 @@ export class Store {
};
constructor() {
this.defaults.general.language = this.checkLocale(app.getLocale().replace("-", "_")) ?? "en_US";
Store.cfg = new ElectronStore({
name: "cider-config",
defaults: this.defaults,

View file

@ -42,6 +42,7 @@
"components/moreinfo-modal",
"components/equalizer",
"components/add-to-playlist",
"components/smarthints",
"components/queue",
"components/mediaitem-scroller-horizontal",
"components/mediaitem-scroller-horizontal-large",

View file

@ -208,9 +208,7 @@ export class wsapi {
response.message = "Next";
break;
case "previous":
this._win.webContents.executeJavaScript(
`if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null) {MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex)}`
);
this._win.webContents.executeJavaScript(`if (MusicKit.getInstance().queue.previousPlayableItemIndex != -1 && MusicKit.getInstance().queue.previousPlayableItemIndex != null) {MusicKit.getInstance().changeToMediaAtIndex(MusicKit.getInstance().queue.previousPlayableItemIndex)}`);
response.message = "Previous";
break;
case "musickit-api":

View file

@ -1,18 +1,17 @@
require("v8-compile-cache");
import { app, components, ipcMain } from "electron";
import { join } from "path";
import { app } from "electron";
if (!app.isPackaged) {
app.setPath("userData", join(app.getPath("appData"), "Cider"));
}
import { Store } from "./base/store";
import { AppEvents } from "./base/app";
import { Plugins } from "./base/plugins";
import { BrowserWindow } from "./base/browserwindow";
import { init as Sentry } from "@sentry/electron";
import { RewriteFrames } from "@sentry/integrations";
import { components, ipcMain } from "electron";
if (!app.isPackaged) {
app.setPath("userData", join(app.getPath("appData"), "Cider"));
}
// Analytics for debugging fun yeah.
Sentry({
@ -54,7 +53,9 @@ app.on("ready", () => {
win.on("ready-to-show", () => {
console.debug("[Cider] Window is Ready.");
CiderPlug.callPlugins("onReady", win);
if (!app.commandLine.hasSwitch("hidden")) {
win.show();
}
});
});
});
@ -62,9 +63,11 @@ app.on("ready", () => {
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Renderer Event Handlers
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
let rendererInitialized = false;
ipcMain.handle("renderer-ready", (event) => {
if (rendererInitialized) return;
CiderPlug.callPlugins("onRendererReady", event);
rendererInitialized = true;
});
ipcMain.on("playbackStateDidChange", (_event, attributes) => {
@ -75,6 +78,10 @@ ipcMain.on("nowPlayingItemDidChange", (_event, attributes) => {
CiderPlug.callPlugins("onNowPlayingItemDidChange", attributes);
});
ipcMain.on("playbackTimeDidChange", (_event, attributes) => {
CiderPlug.callPlugins("playbackTimeDidChange", attributes);
});
app.on("before-quit", () => {
CiderPlug.callPlugins("onBeforeQuit");
console.warn(`${app.getName()} exited.`);

View file

@ -2,6 +2,7 @@ import * as electron from "electron";
import * as os from "os";
import { resolve } from "path";
import * as CiderReceiver from "../base/castreceiver";
const MediaRendererClient = require("upnp-mediarenderer-client");
export default class ChromecastPlugin {
/**
@ -28,6 +29,7 @@ export default class ChromecastPlugin {
private connectedHosts: any = {};
private connectedPlayer: any;
private ciderPort: any = 9000;
private scanCount: any = 0;
// private server = false;
// private bufcount = 0;
// private bufcount2 = 0;
@ -67,19 +69,15 @@ export default class ChromecastPlugin {
ssdpBrowser.search("urn:dial-multiscreen-org:device:dial:1");
// // actual upnp devices
// if (app.cfg.get("audio.enableDLNA")) {
// let ssdpBrowser2 = new Client();
// ssdpBrowser2.on('response', (headers, statusCode, rinfo) => {
// var location = getLocation(headers);
// if (location != null) {
// this.getServiceDescription(location, rinfo.address);
// }
// });
// ssdpBrowser2.search('urn:schemas-upnp-org:device:MediaRenderer:1');
// }
// actual upnp devices
let ssdpBrowser2 = new Client();
ssdpBrowser2.on("response", (headers: any, statusCode: any, rinfo: any) => {
var location = getLocation(headers);
if (location != null) {
this.getServiceDescription(location, rinfo.address);
}
});
ssdpBrowser2.search("urn:schemas-upnp-org:device:MediaRenderer:1");
} catch (e) {
console.log("Search GC err", e);
}
@ -202,11 +200,7 @@ export default class ChromecastPlugin {
if (details.family === "IPv4" && !details.internal) {
if (!/(loopback|vmware|internal|hamachi|vboxnet|virtualbox)/gi.test(dev + (alias ? ":" + alias : ""))) {
if (details.address.substring(0, 8) === "192.168." || details.address.substring(0, 7) === "172.16." || details.address.substring(0, 3) === "10.") {
if (
!ip.startsWith("192.168.") ||
(ip2.startsWith("192.168.") && !ip.startsWith("192.168.") && ip2.startsWith("172.16.") && !ip.startsWith("192.168.") && !ip.startsWith("172.16.")) ||
(ip2.startsWith("10.") && !ip.startsWith("192.168.") && !ip.startsWith("172.16.") && !ip.startsWith("10."))
) {
if (!ip.startsWith("192.168.") || (ip2.startsWith("192.168.") && !ip.startsWith("192.168.") && ip2.startsWith("172.16.") && !ip.startsWith("192.168.") && !ip.startsWith("172.16.")) || (ip2.startsWith("10.") && !ip.startsWith("192.168.") && !ip.startsWith("172.16.") && !ip.startsWith("10."))) {
ip = details.address;
}
++alias;
@ -257,26 +251,29 @@ export default class ChromecastPlugin {
});
} else {
// upnp devices
//try {
// client = new MediaRendererClient(UPNPDesc);
// const options = {
// autoplay: true,
// contentType: 'audio/x-wav',
// dlnaFeatures: 'DLNA.ORG_PN=-;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000',
// metadata: {
// title: 'Apple Music Electron',
// creator: 'Streaming ...',
// type: 'audio', // can be 'video', 'audio' or 'image'
// // url: 'http://' + getIp() + ':' + server.address().port + '/',
// // protocolInfo: 'DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000;
// }
// };
// client.load('http://' + getIp() + ':' + server.address().port + '/a.wav', options, function (err, _result) {
// if (err) throw err;
// console.log('playing ...');
// });
// } catch (e) {
// }
try {
let client = new MediaRendererClient(UPNPDesc);
const options = {
autoplay: true,
contentType: "audio/x-wav",
dlnaFeatures: "DLNA.ORG_PN=-;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000",
metadata: {
title: "Cider",
creator: "Streaming ...",
type: "audio", // can be 'video', 'audio' or 'image'
// url: 'http://' + getIp() + ':' + server.address().port + '/',
// protocolInfo: 'DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000;
},
};
client.load("http://" + this.getIp() + ":" + this.ciderPort + "/audio.wav", options, function (err: any, _result: any) {
if (err) throw err;
console.log("playing ...");
});
if (!this.connectedHosts[device.host]) {
this.connectedHosts[device.host] = client;
this.activeConnections.push(client);
}
} catch (e) {}
}
}
@ -318,6 +315,10 @@ export default class ChromecastPlugin {
});
electron.ipcMain.on("getChromeCastDevices", (_event, _data) => {
if (this.scanCount++ == 2) {
this.scanCount = 0;
this.castDevices = [];
}
this.searchForGCDevices();
});

View file

@ -48,15 +48,21 @@ export default class DiscordRPC {
* Runs on app ready
*/
onReady(_win: any): void {
const self = this;
this.connect();
console.debug(`[Plugin][${this.name}] Ready.`);
ipcMain.on("updateRPCImage", async (_event, imageurl) => {
}
/**
* Set up ipc listeners for the plugin
*/
onRendererReady() {
const self = this;
ipcMain.on("discordrpc:updateImage", async (_event, imageurl) => {
if (!this._utils.getStoreValue("general.privateEnabled")) {
let b64data = "";
let postbody = "";
if (imageurl.startsWith("/ciderlocalart")) {
let port = await _win.webContents.executeJavaScript(`app.clientPort`);
let port = await this._utils.getWindow().webContents.executeJavaScript(`app.clientPort`);
console.log("http://localhost:" + port + imageurl);
const response = await fetch("http://localhost:" + port + imageurl);
b64data = (await response.buffer()).toString("base64");
@ -66,7 +72,7 @@ export default class DiscordRPC {
body: postbody,
headers: {
"Content-Type": "application/json",
"User-Agent": _win.webContents.getUserAgent(),
"User-Agent": this._utils.getWindow().webContents.getUserAgent(),
},
})
.then((res) => res.json())
@ -81,7 +87,7 @@ export default class DiscordRPC {
body: postbody,
headers: {
"Content-Type": "application/json",
"User-Agent": _win.webContents.getUserAgent(),
"User-Agent": this._utils.getWindow().webContents.getUserAgent(),
},
})
.then((res) => res.json())
@ -92,17 +98,22 @@ export default class DiscordRPC {
}
}
});
ipcMain.on("reloadRPC", () => {
ipcMain.on("discordrpc:reload", (_event, configUpdate = null) => {
console.log(`[DiscordRPC][reload] Reloading DiscordRPC.`);
this._client.destroy();
if (this._client) {
this._client.destroy();
}
if (!this._utils.getStoreValue("connectivity.discord_rpc.enabled")) return;
this._client
.endlessLogin({
clientId: this._utils.getStoreValue("connectivity.discord_rpc.client") === "Cider" ? "911790844204437504" : "886578863147192350",
})
.then(() => {
console.log(`[DiscordRPC][reload] DiscordRPC Reloaded.`);
this.ready = true;
this._utils.getWindow().webContents.send("rpcReloaded", this._client.user);
if (configUpdate == null) this._utils.getWindow().webContents.send("rpcReloaded", this._client.user);
if (this._activityCache && this._activityCache.details && this._activityCache.state) {
console.info(`[DiscordRPC][reload] Restoring activity cache.`);
this._client.setActivity(this._activityCache);
@ -111,6 +122,13 @@ export default class DiscordRPC {
.catch((e: any) => console.error(`[DiscordRPC][reload] ${e}`));
// this.connect(true)
});
ipcMain.on("onPrivacyModeChange", (_event, enabled) => {
if (enabled && this._client) {
this._client.clearActivity();
} else if (!enabled && this._activityCache && this._activityCache.details && this._activityCache.state) {
this._client.setActivity(this._activityCache);
}
});
}
/**
@ -133,7 +151,7 @@ export default class DiscordRPC {
* Runs on song change
* @param attributes Music Attributes
*/
onNowPlayingItemDidChange(attributes: object): void {
playbackTimeDidChange(attributes: object): void {
this._attributes = attributes;
this.setActivity(attributes);
}
@ -158,7 +176,7 @@ export default class DiscordRPC {
this._client.once("ready", () => {
console.info(`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${this._client.user.id}.`);
if (this._activityCache && this._activityCache.details && this._activityCache.state) {
if (this._activityCache && this._activityCache.details && this._activityCache.state && !this._utils.getStoreValue("general.privateEnabled")) {
console.info(`[DiscordRPC][connect] Restoring activity cache.`);
this._client.setActivity(this._activityCache);
}
@ -180,14 +198,14 @@ export default class DiscordRPC {
* @param attributes Music Attributes
*/
private setActivity(attributes: any) {
if (!this._client) {
if (!this._client || !attributes) {
return;
}
// Check if show buttons is (true) or (false)
let activity: Object = {
details: this._utils.getStoreValue("connectivity.discord_rpc.details_format"),
state: this._utils.getStoreValue("connectivity.discord_rpc.state_format"),
details: this._utils.getStoreValue("connectivity.discord_rpc.activity.details_format"),
state: this._utils.getStoreValue("connectivity.discord_rpc.activity.state_format"),
largeImageKey: attributes?.artwork?.url?.replace("{w}", "1024").replace("{h}", "1024"),
largeImageText: attributes.albumName,
instance: false, // Whether the activity is in a game session
@ -201,10 +219,14 @@ export default class DiscordRPC {
return;
}
// Set the activity
if (!activity) {
return;
}
if (!attributes.status && this._utils.getStoreValue("connectivity.discord_rpc.clear_on_pause")) {
this._client.clearActivity();
} else if (activity && this._activityCache !== activity) {
if (this._utils.getStoreValue("general.privateEnabled")) return;
this._client.setActivity(activity);
}
this._activityCache = activity;
@ -214,16 +236,29 @@ export default class DiscordRPC {
* Filter the Discord activity object
*/
private filterActivity(activity: any, attributes: any): Object {
// Add the buttons if people want them
if (!this._utils.getStoreValue("connectivity.discord_rpc.hide_buttons")) {
// Handling Activity Buttons
if (this._utils.getStoreValue("connectivity.discord_rpc.activity.buttons.enabled") && this._utils.getStoreValue("connectivity.discord_rpc.activity.buttons.first") != "disabled") {
const activityUrls: { [key: string]: any } = {
listenOnCider: "cider",
viewOnAppleMusic: "appleMusic",
viewOnOtherMusicServices: "songLink",
};
const firstActivity = this._utils.getLocale(this._utils.getStoreValue("general.language"), `settings.option.connectivity.discordRPC.buttons.${this._utils.getStoreValue("connectivity.discord_rpc.activity.buttons.first")}`);
const secondActivity = this._utils.getLocale(this._utils.getStoreValue("general.language"), `settings.option.connectivity.discordRPC.buttons.${this._utils.getStoreValue("connectivity.discord_rpc.activity.buttons.second")}`);
if (this._utils.getStoreValue("connectivity.discord_rpc.activity.buttons.second") != "disabled") {
activity.buttons = [
{ label: "Listen on Cider", url: attributes.url.cider },
{ label: "View on Apple Music", url: attributes.url.appleMusic },
]; //To change attributes.url => preload/cider-preload.js
{ label: firstActivity, url: attributes.url[activityUrls[this._utils.getStoreValue("connectivity.discord_rpc.activity.buttons.first")]] },
{ label: secondActivity, url: attributes.url[activityUrls[this._utils.getStoreValue("connectivity.discord_rpc.activity.buttons.second")]] },
];
} else {
activity.buttons = [{ label: firstActivity, url: attributes.url[activityUrls[this._utils.getStoreValue("connectivity.discord_rpc.activity.buttons.first")]] }];
}
}
// Add the timestamp if its playing and people want them
if (!this._utils.getStoreValue("connectivity.discord_rpc.hide_timestamp") && attributes.status) {
if (!this._utils.getStoreValue("connectivity.discord_rpc.hide_timestamp") && attributes.status && new Date(attributes.endTime).getTime() > 0 && isFinite(attributes.endTime) && isFinite(attributes.startTime)) {
activity.startTimestamp = Date.now() - (attributes?.durationInMillis - attributes?.remainingTime);
activity.endTimestamp = attributes.endTime;
}
@ -280,12 +315,6 @@ export default class DiscordRPC {
activity.largeImageKey = "cider";
}
// Timestamp
if (new Date(attributes.endTime).getTime() < 0) {
delete activity.startTime;
delete activity.endTime;
}
// not sure
if (!attributes.artistName) {
delete activity.state;
@ -294,6 +323,10 @@ export default class DiscordRPC {
if (!activity.largeImageText || activity.largeImageText.length < 2) {
delete activity.largeImageText;
}
if (activity.status === "" || activity.details === "") {
return false;
}
return activity;
}
}

View file

@ -29,7 +29,9 @@ export default class lastfm {
onReady(_win: Electron.BrowserWindow): void {
this.initializeLastFM("", this._apiCredentials);
}
onRendererReady(): void {
// Register the ipcMain handlers
this._utils.getIPCMain().handle("lastfm:url", (event: any) => {
console.debug(`[${lastfm.name}:url] Called.`);
@ -48,32 +50,21 @@ export default class lastfm {
});
this._utils.getIPCMain().on("lastfm:nowPlayingChange", (event: any, attributes: any) => {
if (this._utils.getStoreValue("connectivity.lastfm.filter_loop") || this._utils.getStoreValue("general.privateEnabled")) return;
if (this._utils.getStoreValue("connectivity.lastfm.filter_loop") || this._utils.getStoreValue("general.privateEnabled") || attributes.type === "radioStation") return;
this.updateNowPlayingTrack(attributes);
});
this._utils.getIPCMain().on("lastfm:FilteredNowPlayingItemDidChange", (event: any, attributes: any) => {
if (this._utils.getStoreValue("general.privateEnabled") || attributes.type === "radioStation") return;
this.updateNowPlayingTrack(attributes);
});
this._utils.getIPCMain().on("lastfm:scrobbleTrack", (event: any, attributes: any) => {
if (this._utils.getStoreValue("general.privateEnabled")) return;
if (this._utils.getStoreValue("general.privateEnabled") || attributes.type === "radioStation") return;
this.scrobbleTrack(attributes);
});
}
/**
* Runs on playback State Change
* @param attributes Music Attributes (attributes.status = current state)
*/
onPlaybackStateDidChange(attributes: object): void {}
/**
* Runs on song change
* @param attributes Music Attributes
* @param scrobble
*/
onNowPlayingItemDidChange(attributes: any, scrobble = false): void {
if (this._utils.getStoreValue("general.privateEnabled")) return;
this.updateNowPlayingTrack(attributes);
}
/**
* Initialize LastFM
* @param token
@ -179,13 +170,7 @@ export default class lastfm {
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;
if (!this._authenticated || !attributes || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.type] || (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._scrobbleCache.track === attributes.lfmTrack.name)) return;
// Scrobble
const scrobble = {
@ -225,13 +210,9 @@ export default class lastfm {
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;
if (!this._authenticated || !attributes || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.playParams.kind] || this._utils.getStoreValue("connectivity.lastfm.filter_types")[attributes.type] || (this._utils.getStoreValue("connectivity.lastfm.filter_loop") && this._nowPlayingCache.track === attributes.lfmTrack.name)) return;
console.log(this._utils.getStoreValue("connectivity.lastfm.filter_types"));
const nowPlaying = {
artist: attributes.lfmTrack.artist.name,

View file

@ -53,20 +53,7 @@ export default class Thumbar {
{
label: utils.getLocale(utils.getStoreValue("general.language"), "menubar.options.view"),
submenu: [
...(this.isMac
? [
{ role: "reload" },
{ role: "forceReload" },
{ role: "toggleDevTools" },
{ type: "separator" },
{ role: "resetZoom" },
{ role: "zoomIn" },
{ role: "zoomOut" },
{ type: "separator" },
{ role: "togglefullscreen" },
{ type: "separator" },
]
: []),
...(this.isMac ? [{ role: "reload" }, { role: "forceReload" }, { role: "toggleDevTools" }, { type: "separator" }, { role: "resetZoom" }, { role: "zoomIn" }, { role: "zoomOut" }, { type: "separator" }, { role: "togglefullscreen" }, { type: "separator" }] : []),
{
label: utils.getLocale(utils.getStoreValue("general.language"), "term.search"),
accelerator: utils.getStoreValue("general.keybindings.search").join("+"),

View file

@ -27,7 +27,6 @@ export default class mpris {
*/
constructor(utils: any) {
mpris.utils = utils;
console.debug(`[Plugin][${mpris.name}] Loading Complete.`);
}
@ -103,7 +102,7 @@ export default class mpris {
*/
private static updateMetaData(attributes: any) {
mpris.player.metadata = {
"mpris:trackid": mpris.player.objectPath(`track/${attributes.playParams.id.replace(/[.]+/g, "")}`),
"mpris:trackid": mpris.player.objectPath(`track/${attributes.playParams.id.replace(/[^a-zA-Z 0-9]+/g, "")}`),
"mpris:length": attributes.durationInMillis * 1000, // In microseconds
"mpris:artUrl": attributes.artwork.url.replace("/{w}x{h}bb", "/512x512bb").replace("/2000x2000bb", "/35x35bb"),
"xesam:title": `${attributes.name}`,
@ -170,7 +169,7 @@ export default class mpris {
* @param attributes Music Attributes
*/
@mpris.linuxOnly
onNowPlayingItemDidChange(attributes: object): void {
playbackTimeDidChange(attributes: object): void {
mpris.updateMetaData(attributes);
}
}

View file

@ -23,7 +23,7 @@ export default class RAOP {
private portairplay: any = "";
private airtunes: any;
private device: any;
// private device: any;
private mdns = require("mdns-js");
private ok: any = 1;
private devices: any = [];
@ -89,7 +89,7 @@ export default class RAOP {
private ondeviceup(name: any, host: any, port: any, addresses: any, text: any, airplay2: any = null) {
// console.log(this.castDevices.findIndex((item: any) => {return (item.name == host.replace(".local","") && item.port == port )}))
let shown_name = (host ?? "Unknown").replace(".local", "");
let shown_name = name;
try {
let model = text.filter((u: any) => String(u).startsWith("model="));
let manufacturer = text.filter((u: any) => String(u).startsWith("manufacturer="));
@ -105,7 +105,7 @@ export default class RAOP {
if (
this.castDevices.findIndex((item: any) => {
return item != null && item.name == shown_name && item.port == port && item.host == host_name && item.host != "Unknown";
return item != null && item.name == shown_name && item.host == host_name && item.host != "Unknown";
}) == -1
) {
this.castDevices.push({
@ -158,8 +158,8 @@ export default class RAOP {
});
electron.ipcMain.on("getAirplayDevice", (event, data) => {
// this.castDevices = [];
console.log("scan for airplay devices");
this.castDevices = [];
console.debug("scan for airplay devices");
const browser = this.mdns.createBrowser(this.mdns.tcp("raop"));
browser.on("ready", browser.discover);
@ -168,9 +168,10 @@ export default class RAOP {
if (service.addresses && service.fullname && service.fullname.includes("_raop._tcp")) {
// console.log(service.txt)
this._win.webContents.executeJavaScript(`console.log(
"${service.name} ${service.host}:${service.port} ${service.addresses}"
"${service.name} ${service.host}:${service.port} ${service.addresses} ${service.fullname}"
)`);
this.ondeviceup(service.name, service.host, service.port, service.addresses, service.txt);
let itemname = service.fullname.substring(service.fullname.indexOf("@") + 1, service.fullname.indexOf("._raop._tcp"));
this.ondeviceup(itemname, service.host, service.port, service.addresses, service.txt);
}
});
@ -183,7 +184,8 @@ export default class RAOP {
this._win.webContents.executeJavaScript(`console.log(
"${service.name} ${service.host}:${service.port} ${service.addresses}"
)`);
this.ondeviceup(service.name, service.host, service.port, service.addresses, service.txt, true);
let itemname = service.fullname.substring(service.fullname.indexOf("@") + 1, service.fullname.indexOf("._airplay._tcp"));
this.ondeviceup(itemname, service.host, service.port, service.addresses, service.txt, true);
}
});
@ -201,52 +203,73 @@ export default class RAOP {
// });
});
electron.ipcMain.on("performAirplayPCM", (event, ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv) => {
electron.ipcMain.on("performAirplayPCM", (event, ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv, silent) => {
if (ipv4 != this.ipairplay || ipport != this.portairplay) {
if (this.airtunes == null) {
this.airtunes = new this.u();
}
this.ipairplay = ipv4;
this.portairplay = ipport;
this.device = this.airtunes.add(ipv4, {
let identifier = ipv4 + ":" + ipport + "ap";
let idx = this.devices.findIndex((a: any) => {
return a.id == identifier;
});
if (idx != -1) {
delete this.devices[idx];
this.devices = this.devices.filter((n: any) => n); // remove old controller
}
this.devices.push({
id: identifier,
ip: ipv4,
port: ipport,
state: 0,
controller: this.airtunes.add(ipv4, {
port: ipport,
volume: airplay2dv ? 30 : 50,
password: sepassword,
txt: txt,
airplay2: airplay2dv,
debug: true,
debug: null,
forceAlac: false,
}),
});
// console.log('lol',txt)
this.device.on("status", (status: any) => {
idx = this.devices.findIndex((a: any) => {
return a.id == identifier;
});
// console.log('lol', this.devices)
this.devices[idx].controller.on("status", (status: any) => {
console.log("device status", status);
if (status == "ready") {
this._win.webContents.setAudioMuted(true);
this._win.webContents.executeJavaScript(`CiderAudio.sendAudio()`).catch((err: any) => console.error(err));
}
if (status == "need_password") {
this._win.webContents.executeJavaScript(`app.setAirPlayCodeUI()`);
this._win.webContents.executeJavaScript(`app.setAirPlayCodeUI('${this.devices[idx].id}')`);
}
if (status == "pair_success") {
this._win.webContents.executeJavaScript(`app.sendAirPlaySuccess()`);
this._win.webContents.executeJavaScript(`app.sendAirPlaySuccess(${silent},'${this.devices[idx].id}')`);
}
if (status == "pair_failed") {
this._win.webContents.executeJavaScript(`app.sendAirPlayFailed()`);
this.disconnectAirplay(this.devices[idx].id);
}
if (status == "stopped") {
this.airtunes.stopAll(() => {
console.log("end");
});
this._win.webContents.executeJavaScript(`app.airplayDisconnect(true, ${[ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv]})`).catch((err: any) => console.error(err));
this.airtunes = null;
this.device = null;
this.ipairplay = "";
this.portairplay = "";
this.ok = 1;
// this.airtunes.stopAll(() => {
// console.log("end");
// });
if (this.devices[idx]?.state != null && this.devices[idx].state != -1) this._win.webContents.executeJavaScript(`app.airplayDisconnect(true, ${JSON.stringify([ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv])})`).catch((err: any) => console.error(err));
// this.airtunes = null;
// this.device = null;
// this.ipairplay = "";
// this.portairplay = "";
// this.ok = 1;
} else {
setTimeout(() => {
if (this.ok == 1) {
console.log(this.device.key, title ?? "", artist ?? "", album ?? "");
this.airtunes.setTrackInfo(this.device.key, title ?? "", artist ?? "", album ?? "");
console.log(this.devices[idx].controller.key, title ?? "", artist ?? "", album ?? "");
this.airtunes.setTrackInfo(this.devices[idx].controller.key, title ?? "", artist ?? "", album ?? "");
this.uploadImageAirplay(artworkURL);
console.log("done");
this.ok == 2;
@ -257,15 +280,25 @@ export default class RAOP {
}
});
electron.ipcMain.on("setAirPlayPasscode", (event, passcode) => {
if (this.device) {
this.device.setPasscode(passcode);
electron.ipcMain.on("setAirPlayPasscode", (event, passcode, identifier) => {
if (this.devices.length > 0) {
let idx = this.devices.findIndex((a: any) => {
return a.id == identifier;
});
if (idx != -1) {
this.devices[idx].controller.setPasscode(passcode);
}
}
});
electron.ipcMain.on("setAirPlayVolume", (event, volume) => {
if (this.device) {
this.device.setVolume(volume);
electron.ipcMain.on("setAirPlayVolume", (event, volume, identifier) => {
if (this.devices.length > 0) {
let idx = this.devices.findIndex((a: any) => {
return a.id == identifier;
});
if (idx != -1) {
this.devices[idx].controller.setVolume(volume);
}
}
});
@ -318,33 +351,70 @@ export default class RAOP {
}
});
electron.ipcMain.on("disconnectAirplay", (event) => {
this._win.webContents.setAudioMuted(false);
this.airtunes.stopAll(function () {
console.log("end");
});
this._win.webContents.executeJavaScript("app.airplayDisconnect(false)").catch((err: any) => console.error(err));
this.airtunes = null;
this.device = null;
this.ipairplay = "";
this.portairplay = "";
this.ok = 1;
this.i = false;
electron.ipcMain.on("disconnectAirplay", (event, identifier = "") => {
console.log("iden", identifier);
this.disconnectAirplay(identifier);
});
electron.ipcMain.on("updateAirplayInfo", (event, title, artist, album, artworkURL) => {
if (this.airtunes && this.device) {
console.log(this.device.key, title, artist, album);
this.airtunes.setTrackInfo(this.device.key, title, artist, album);
if (this.airtunes && this.devices.length > 0) {
for (let i in this.devices) {
console.log(this.devices[i].controller.key, title, artist, album);
this.airtunes.setTrackInfo(this.devices[i].controller.key, title, artist, album);
}
this.uploadImageAirplay(artworkURL);
}
});
electron.ipcMain.on("updateRPCImage", (_event, imageurl) => {
electron.ipcMain.on("discordrpc:updateImage", (_event, imageurl) => {
this.uploadImageAirplay(imageurl);
});
}
private disconnectAirplay(identifier: any = "") {
console.log("awdas");
this._win.webContents
.executeJavaScript(`app.airplayDisconnect(false, [], '${identifier}')`)
.then(() => {
if (identifier == "") {
if (this.airtunes) {
for (let i in this.devices) {
this.devices[i].state = -1;
}
this.airtunes.stopAll(() => {
console.log("endAll");
this.airtunes = null;
this.devices = [];
});
} else {
this.devices = [];
}
} else {
let idx = this.devices.findIndex((a: any) => {
return a.id == identifier;
});
if (idx != -1) {
this.devices[idx].state = -1;
this.devices[idx].controller.stop(() => {
console.log(this.devices[idx].id, "stopped");
});
delete this.devices[idx];
this.devices = this.devices.filter((n: any) => n);
}
}
if (this.devices.length == 0) {
console.log("cleanup airtunes");
this._win.webContents.setAudioMuted(false);
this.airtunes = null;
this.ipairplay = "";
this.portairplay = "";
this.ok = 1;
this.i = false;
}
})
.catch((err: any) => console.error("lsdsd", err));
}
private uploadImageAirplay = (url: any) => {
try {
if (url != null && url != "") {
@ -352,7 +422,11 @@ export default class RAOP {
fetch(url)
.then((res) => res.buffer())
.then((buffer) => {
this.airtunes.setArtwork(this.device.key, buffer, "image/png");
if (this.airtunes && this.devices.length > 0) {
for (let i in this.devices) {
this.airtunes.setArtwork(this.devices[i].controller.key, buffer, "image/png");
}
}
})
.catch((err) => {
console.log(err);
@ -390,16 +464,19 @@ export default class RAOP {
* @param attributes Music Attributes (attributes.status = current state)
*/
onPlaybackStateDidChange(attributes: any): void {
if (this.airtunes && this.device) {
if (this.airtunes && this.devices.length > 0) {
let title = attributes?.name ?? "";
let artist = attributes?.artistName ?? "";
let album = attributes?.albumName ?? "";
let artworkURL = attributes?.artwork?.url ?? null;
console.log(this.device.key, title, artist, album);
this.airtunes.setTrackInfo(this.device.key, title, artist, album);
if (artworkURL != null) {
for (let i in this.devices) {
console.log(this.devices[i].controller.key, title, artist, album);
this.airtunes.setTrackInfo(this.devices[i].controller.key, title, artist, album);
}
let artworkURL = attributes?.artwork?.url ?? null;
if (artworkURL != null) {
this.uploadImageAirplay(artworkURL.replace("{w}", "1024").replace("{h}", "1024"));
}
}
}
}

View file

@ -9,6 +9,7 @@ const MusicKitInterop = {
/* MusicKit.Events.playbackStateDidChange */
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackStateDidChange, () => {
const attributes = MusicKitInterop.getAttributes();
if (!attributes) return;
if (MusicKitInterop.filterTrack(attributes, true, false)) {
global.ipcRenderer.send("playbackStateDidChange", attributes);
global.ipcRenderer.send("wsapi-updatePlaybackState", attributes);
@ -18,11 +19,12 @@ const MusicKitInterop = {
/* MusicKit.Events.playbackProgressDidChange */
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackProgressDidChange, async () => {
const attributes = MusicKitInterop.getAttributes();
if (!attributes) return;
// wsapi call
ipcRenderer.send("wsapi-updatePlaybackState", attributes);
// lastfm call
if (app.mk.currentPlaybackProgress === app.cfg.connectivity.lastfm.scrobble_after / 100) {
attributes.primaryArtist = app.cfg.connectivity.lastfm.remove_featured ? await this.fetchPrimaryArtist() : attributes.artistName;
attributes.primaryArtist = app.cfg.connectivity.lastfm.remove_featured ? await this.fetchSongRelationships() : attributes.artistName;
ipcRenderer.send("lastfm:scrobbleTrack", attributes);
}
});
@ -30,16 +32,21 @@ const MusicKitInterop = {
/* MusicKit.Events.playbackTimeDidChange */
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackTimeDidChange, () => {
ipcRenderer.send("mpris:playbackTimeDidChange", MusicKit.getInstance()?.currentPlaybackTime * 1000 * 1000 ?? 0);
const attributes = MusicKitInterop.getAttributes();
if (!attributes) return;
ipcRenderer.send("playbackTimeDidChange", attributes);
});
/* MusicKit.Events.nowPlayingItemDidChange */
MusicKit.getInstance().addEventListener(MusicKit.Events.nowPlayingItemDidChange, async () => {
console.debug("[cider:preload] nowPlayingItemDidChange");
const attributes = MusicKitInterop.getAttributes();
attributes.primaryArtist = app.cfg.connectivity.lastfm.remove_featured ? await this.fetchPrimaryArtist() : attributes.artistName;
if (!attributes) return;
attributes.primaryArtist = app.cfg.connectivity.lastfm.remove_featured ? await this.fetchSongRelationships() : attributes.artistName;
global.ipcRenderer.send("nowPlayingItemDidChange", attributes);
if (MusicKitInterop.filterTrack(attributes, false, true)) {
global.ipcRenderer.send("nowPlayingItemDidChange", attributes);
global.ipcRenderer.send("lastfm:FilteredNowPlayingItemDidChange", attributes);
} else if (attributes.name !== "no-title-found" && attributes.playParams.id !== "no-id-found") {
global.ipcRenderer.send("lastfm:nowPlayingChange", attributes);
}
@ -52,6 +59,7 @@ const MusicKitInterop = {
await this.sleep(750);
MusicKit.getInstance().playbackRate = app.cfg.audio.playbackRate;
}
console.debug("[cider:preload] nowPlayingItemDidChange");
});
/* MusicKit.Events.authorizationStatusDidChange */
@ -81,31 +89,47 @@ const MusicKitInterop = {
});
},
async fetchPrimaryArtist() {
const songID = app.mk.nowPlayingItem.attributes.playParams.catalogId || app.mk.nowPlayingItem.attributes.playParams.id;
const res = await MusicKit.getInstance().api.v3.music("/v1/catalog/" + MusicKit.getInstance().storefrontId + `/songs/${songID}`, {
async fetchSongRelationships({ id = this.getAttributes().songId, relationship = "primaryName" } = {}) {
if (!id) return null;
const res = await MusicKit.getInstance().api.v3.music("/v1/catalog/" + MusicKit.getInstance().storefrontId + `/songs/${id}`, {
include: {
songs: ["artists"],
},
});
if (!res || !res.data) {
console.warn("[cider:preload] fetchPrimaryArtist: no response");
return app.mk.nowPlayingItem.attributes.artistName;
console.warn("[cider:preload] fetchSongRelationships: no response");
if (id === this.getAttributes().songId) {
return this.getAttributes().artistName;
}
}
if (!res.data.data.length) {
console.error(`[cider:preload] fetchSongRelationships: Unable to locate song with id of ${id}`);
if (id === this.getAttributes().songId) {
return this.getAttributes().artistName;
}
}
if (!res.data.data.length) {
console.error(`[cider:preload] fetchPrimaryArtist: Unable to locate song with id of ${songID}`);
return app.mk.nowPlayingItem.attributes.artistName;
}
const songData = res.data.data[0];
const artistData = songData.relationships.artists.data;
const albumData = songData.relationships.albums.data;
const primaryArtist = artistData[0];
switch (relationship) {
default:
case "primaryName":
if (artistData.length < 1) {
console.error(`[cider:preload] fetchPrimaryArtist: Unable to find artists related to the song with id of ${songID}`);
console.error(`[cider:preload] fetchSongRelationships: Unable to find artists related to the song with id of ${id}`);
return app.mk.nowPlayingItem.attributes.artistName;
}
const primaryArtist = artistData[0];
return primaryArtist.attributes.name;
case "primaryArtist":
return primaryArtist;
case "album":
return albumData[0];
}
},
getAttributes: function () {
@ -117,6 +141,7 @@ const MusicKitInterop = {
const attributes = nowPlayingItem != null ? nowPlayingItem.attributes : {};
attributes.songId = attributes.songId ?? attributes.playParams?.catalogId ?? attributes.playParams?.id;
attributes.type = nowPlayingItem?.type ?? "";
attributes.status = isPlayingExport ?? null;
attributes.name = attributes?.name ?? "no-title-found";
attributes.artwork = attributes?.artwork ?? { url: "" };
@ -126,6 +151,7 @@ const MusicKitInterop = {
attributes.url = {
cider: `https://cider.sh/link?play/s/${nowPlayingItem?._songId ?? nowPlayingItem?.songId ?? "no-id-found"}`,
appleMusic: attributes.websiteUrl ? attributes.websiteUrl : `https://music.apple.com/${mk.storefrontId}/song/${nowPlayingItem?._songId ?? nowPlayingItem?.songId ?? "no-id-found"}`,
songLink: "https://song.link/i/" + attributes.songId,
};
if (attributes.playParams.id === "no-id-found") {
attributes.playParams.id = nowPlayingItem?.id ?? "no-id-found";
@ -139,6 +165,10 @@ const MusicKitInterop = {
attributes.currentPlaybackProgress = currentPlaybackProgress ?? 0;
attributes.startTime = Date.now();
attributes.endTime = Math.round(attributes?.playParams?.id === cache.playParams.id ? Date.now() + attributes?.remainingTime : attributes?.startTime + attributes?.durationInMillis);
if (attributes.name === "no-title-found") {
return;
}
return attributes;
},

View file

@ -301,9 +301,7 @@ const CiderAudio = {
filterlessGain = filterlessGain * spatialProfile.gainComp;
}
filterlessGain = Math.pow(10, (-1 * (20 * Math.log10(filterlessGain))) / 20).toFixed(4);
filterlessGain > 1.0
? CiderAudio.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(1.0, CiderAudio.context.currentTime + 0.3)
: CiderAudio.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(filterlessGain, CiderAudio.context.currentTime + 0.3);
filterlessGain > 1.0 ? CiderAudio.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(1.0, CiderAudio.context.currentTime + 0.3) : CiderAudio.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(filterlessGain, CiderAudio.context.currentTime + 0.3);
console.debug(`[Cider][Audio] IntelliGainComp: ${filterlessGain > 1.0 ? 0 : (20 * Math.log10(filterlessGain)).toFixed(2)} dB (${filterlessGain > 1.0 ? 1 : filterlessGain})`);
return;
}
@ -358,9 +356,7 @@ const CiderAudio = {
maxGain = maxGain * spatialProfile.gainComp;
}
maxGain = Math.pow(10, (-1 * (20 * Math.log10(maxGain))) / 20).toFixed(4);
maxGain > 1.0
? CiderAudio.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(1.0, CiderAudio.context.currentTime + 0.3)
: CiderAudio.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(maxGain, CiderAudio.context.currentTime + 0.3);
maxGain > 1.0 ? CiderAudio.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(1.0, CiderAudio.context.currentTime + 0.3) : CiderAudio.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(maxGain, CiderAudio.context.currentTime + 0.3);
console.debug(`[Cider][Audio] IntelliGainComp: ${maxGain > 1.0 ? 0 : (20 * Math.log10(maxGain)).toFixed(2)} dB (${maxGain > 1.0 ? 1 : maxGain})`);
},
sendAudio: function () {
@ -397,7 +393,7 @@ const CiderAudio = {
constructor() {
super();
this._bufferSize = 1024;
this._bufferSize = 2048;
this._buffers = null;
this._initBuffer();
}
@ -839,49 +835,49 @@ const CiderAudio = {
switch (destination) {
case "spatial":
try {
CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.spatialNode);
CiderAudio.audioNodes.llpw[CiderAudio.audioNodes.llpw.length - 1].connect(CiderAudio.audioNodes.spatialNode);
console.debug("[Cider][Audio] llpw_n1 -> Spatial");
} catch (e) {}
break;
case "n6":
try {
CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer2);
CiderAudio.audioNodes.llpw[CiderAudio.audioNodes.llpw.length - 1].connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer2");
} catch (e) {}
break;
case "n5":
try {
CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer1);
CiderAudio.audioNodes.llpw[CiderAudio.audioNodes.llpw.length - 1].connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] llpw_n1 -> atmosphereRealizer1");
} catch (e) {}
break;
case "n4":
try {
CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.vibrantbassNode[0]);
CiderAudio.audioNodes.llpw[CiderAudio.audioNodes.llpw.length - 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]);
CiderAudio.audioNodes.llpw[CiderAudio.audioNodes.llpw.length - 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);
CiderAudio.audioNodes.llpw[CiderAudio.audioNodes.llpw.length - 1].connect(CiderAudio.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] llpw_n1 -> opportunisticCorrection");
} catch (e) {}
break;
case "n1":
try {
CiderAudio.audioNodes.llpw.at(-1).connect(CiderAudio.audioNodes.llpw[0]);
CiderAudio.audioNodes.llpw[CiderAudio.audioNodes.llpw.length - 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);
CiderAudio.audioNodes.llpw[CiderAudio.audioNodes.llpw.length - 1].connect(CiderAudio.context.destination);
console.debug("[Cider][Audio] llpw_n1 -> destination");
} catch (e) {}
break;
@ -910,50 +906,50 @@ const CiderAudio = {
switch (destination) {
case "spatial":
try {
CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.spatialNode);
CiderAudio.audioNodes.vibrantbassNode[CiderAudio.audioNodes.vibrantbassNode.length - 1].connect(CiderAudio.audioNodes.spatialNode);
console.debug("[Cider][Audio] vibrantbass_n4 -> Spatial");
} catch (e) {}
break;
case "n6":
try {
CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer2);
CiderAudio.audioNodes.vibrantbassNode[CiderAudio.audioNodes.vibrantbassNode.length - 1].connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] vibrantbass_n4 -> atmosphereRealizer2");
} catch (e) {}
break;
case "n5":
try {
CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer1);
CiderAudio.audioNodes.vibrantbassNode[CiderAudio.audioNodes.vibrantbassNode.length - 1].connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] vibrantbass_n4 -> atmosphereRealizer1");
} catch (e) {}
break;
case "n4":
try {
CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.vibrantbassNode[0]);
CiderAudio.audioNodes.vibrantbassNode[CiderAudio.audioNodes.vibrantbassNode.length - 1].connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] vibrantbass_n4 -> vibrantbassNode");
} catch (e) {}
break;
case "n3":
try {
CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.audioBands[0]);
CiderAudio.audioNodes.vibrantbassNode[CiderAudio.audioNodes.vibrantbassNode.length - 1].connect(CiderAudio.audioNodes.audioBands[0]);
console.debug("[Cider][Audio] vibrantbass_n4 -> audioBands");
} catch (e) {}
break;
case "n2":
try {
CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.opportunisticCorrection);
CiderAudio.audioNodes.vibrantbassNode[CiderAudio.audioNodes.vibrantbassNode.length - 1].connect(CiderAudio.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] vibrantbass_n4 -> opportunisticCorrection");
} catch (e) {}
break;
case "n1":
try {
CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.audioNodes.llpw[0]);
CiderAudio.audioNodes.vibrantbassNode[CiderAudio.audioNodes.vibrantbassNode.length - 1].connect(CiderAudio.audioNodes.llpw[0]);
console.debug("[Cider][Audio] vibrantbass_n4 -> llpw");
} catch (e) {}
break;
case "n0":
try {
CiderAudio.audioNodes.vibrantbassNode.at(-1).connect(CiderAudio.context.destination);
CiderAudio.audioNodes.vibrantbassNode[CiderAudio.audioNodes.vibrantbassNode.length - 1].connect(CiderAudio.context.destination);
console.debug("[Cider][Audio] vibrantbass_n4 -> destination");
} catch (e) {}
break;
@ -1168,48 +1164,48 @@ const CiderAudio = {
switch (destination) {
case "spatial":
CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.spatialNode);
CiderAudio.audioNodes.audioBands[CiderAudio.audioNodes.audioBands.length - 1].connect(CiderAudio.audioNodes.spatialNode);
console.debug("[Cider][Audio] Equalizer -> Spatial");
break;
case "n6":
try {
CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer2);
CiderAudio.audioNodes.audioBands[CiderAudio.audioNodes.audioBands.length - 1].connect(CiderAudio.audioNodes.atmosphereRealizer2);
console.debug("[Cider][Audio] Equalizer -> atmosphereRealizer2");
} catch (e) {}
break;
case "n5":
try {
CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.atmosphereRealizer1);
CiderAudio.audioNodes.audioBands[CiderAudio.audioNodes.audioBands.length - 1].connect(CiderAudio.audioNodes.atmosphereRealizer1);
console.debug("[Cider][Audio] Equalizer -> atmosphereRealizer1");
} catch (e) {}
break;
case "n4":
try {
CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.vibrantbassNode[0]);
CiderAudio.audioNodes.audioBands[CiderAudio.audioNodes.audioBands.length - 1].connect(CiderAudio.audioNodes.vibrantbassNode[0]);
console.debug("[Cider][Audio] Equalizer -> vibrantbassNode");
} catch (e) {}
break;
case "n3":
try {
CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.audioBands[0]);
CiderAudio.audioNodes.audioBands[CiderAudio.audioNodes.audioBands.length - 1].connect(CiderAudio.audioNodes.audioBands[0]);
console.debug("[Cider][Audio] Equalizer -> audioBands");
} catch (e) {}
break;
case "n2":
try {
CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.opportunisticCorrection);
CiderAudio.audioNodes.audioBands[CiderAudio.audioNodes.audioBands.length - 1].connect(CiderAudio.audioNodes.opportunisticCorrection);
console.debug("[Cider][Audio] Equalizer -> opportunisticCorrection");
} catch (e) {}
break;
case "n1":
try {
CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.audioNodes.llpw[0]);
CiderAudio.audioNodes.audioBands[CiderAudio.audioNodes.audioBands.length - 1].connect(CiderAudio.audioNodes.llpw[0]);
console.debug("[Cider][Audio] Equalizer -> llpw");
} catch (e) {}
break;
case "n0":
try {
CiderAudio.audioNodes.audioBands.at(-1).connect(CiderAudio.context.destination);
CiderAudio.audioNodes.audioBands[CiderAudio.audioNodes.audioBands.length - 1].connect(CiderAudio.context.destination);
console.debug("[Cider][Audio] Equalizer -> destination");
} catch (e) {}
break;

View file

@ -171,9 +171,7 @@ const CiderAudioRenderer = {
maxGain = maxGain * spatialProfile.gainComp;
}
maxGain = Math.pow(10, (-1 * (20 * Math.log10(maxGain))) / 20).toFixed(4);
maxGain > 1.0
? CiderAudioRenderer.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(1.0, CiderAudioRenderer.context.currentTime + 0.3)
: CiderAudioRenderer.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(maxGain, CiderAudioRenderer.context.currentTime + 0.3);
maxGain > 1.0 ? CiderAudioRenderer.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(1.0, CiderAudioRenderer.context.currentTime + 0.3) : CiderAudioRenderer.audioNodes.intelliGainComp.gain.exponentialRampToValueAtTime(maxGain, CiderAudioRenderer.context.currentTime + 0.3);
console.debug(`[Cider][Audio] IntelliGainComp: ${maxGain > 1.0 ? 0 : (20 * Math.log10(maxGain)).toFixed(2)} dB (${maxGain > 1.0 ? 1 : maxGain})`);
},
atmosphereRealizer2_n6: function (status, destination) {

View file

@ -1589,9 +1589,7 @@ typeof window !== "undefined" &&
}
var levelDetails = levelInfo.details;
var avgDuration =
(partCurrent ? (levelDetails === null || levelDetails === void 0 ? void 0 : levelDetails.partTarget) : levelDetails === null || levelDetails === void 0 ? void 0 : levelDetails.averagetargetduration) ||
currentFragDuration;
var avgDuration = (partCurrent ? (levelDetails === null || levelDetails === void 0 ? void 0 : levelDetails.partTarget) : levelDetails === null || levelDetails === void 0 ? void 0 : levelDetails.averagetargetduration) || currentFragDuration;
var adjustedbw = void 0; // follow algorithm captured from stagefright :
// https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/httplive/LiveSession.cpp
// Pick the highest bandwidth stream below or equal to estimated bandwidth.
@ -1607,9 +1605,7 @@ typeof window !== "undefined" &&
var bitrate = levels[i].maxBitrate;
var fetchDuration = (bitrate * avgDuration) / adjustedbw;
_utils_logger__WEBPACK_IMPORTED_MODULE_6__["logger"].trace(
"level/adjustedbw/bitrate/avgDuration/maxFetchDuration/fetchDuration: " + i + "/" + Math.round(adjustedbw) + "/" + bitrate + "/" + avgDuration + "/" + maxFetchDuration + "/" + fetchDuration
); // if adjusted bw is greater than level bitrate AND
_utils_logger__WEBPACK_IMPORTED_MODULE_6__["logger"].trace("level/adjustedbw/bitrate/avgDuration/maxFetchDuration/fetchDuration: " + i + "/" + Math.round(adjustedbw) + "/" + bitrate + "/" + avgDuration + "/" + maxFetchDuration + "/" + fetchDuration); // if adjusted bw is greater than level bitrate AND
if (
adjustedbw > bitrate && // fragment fetchDuration unknown OR live stream OR fragment fetchDuration less than max allowed fetch duration, then this level matches
@ -1971,11 +1967,7 @@ typeof window !== "undefined" &&
if (this.bufferFlushed) {
this.bufferFlushed = false;
this.afterBufferFlushed(
this.mediaBuffer ? this.mediaBuffer : this.media,
_loader_fragment__WEBPACK_IMPORTED_MODULE_7__["ElementaryStreamTypes"].AUDIO,
_types_loader__WEBPACK_IMPORTED_MODULE_6__["PlaylistLevelType"].AUDIO
);
this.afterBufferFlushed(this.mediaBuffer ? this.mediaBuffer : this.media, _loader_fragment__WEBPACK_IMPORTED_MODULE_7__["ElementaryStreamTypes"].AUDIO, _types_loader__WEBPACK_IMPORTED_MODULE_6__["PlaylistLevelType"].AUDIO);
}
var bufferInfo = this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer : this.media, _types_loader__WEBPACK_IMPORTED_MODULE_6__["PlaylistLevelType"].AUDIO);
@ -2024,10 +2016,7 @@ typeof window !== "undefined" &&
return;
}
if (
((_frag$decryptdata = frag.decryptdata) === null || _frag$decryptdata === void 0 ? void 0 : _frag$decryptdata.keyFormat) === "identity" &&
!((_frag$decryptdata2 = frag.decryptdata) !== null && _frag$decryptdata2 !== void 0 && _frag$decryptdata2.key)
) {
if (((_frag$decryptdata = frag.decryptdata) === null || _frag$decryptdata === void 0 ? void 0 : _frag$decryptdata.keyFormat) === "identity" && !((_frag$decryptdata2 = frag.decryptdata) !== null && _frag$decryptdata2 !== void 0 && _frag$decryptdata2.key)) {
this.loadKey(frag, trackDetails);
} else {
this.loadFragment(frag, trackDetails, targetBufferTime);
@ -2176,12 +2165,7 @@ typeof window !== "undefined" &&
var transmuxer = this.transmuxer;
if (!transmuxer) {
transmuxer = this.transmuxer = new _demux_transmuxer_interface__WEBPACK_IMPORTED_MODULE_9__["default"](
this.CiderHls,
_types_loader__WEBPACK_IMPORTED_MODULE_6__["PlaylistLevelType"].AUDIO,
this._handleTransmuxComplete.bind(this),
this._handleTransmuxerFlush.bind(this)
);
transmuxer = this.transmuxer = new _demux_transmuxer_interface__WEBPACK_IMPORTED_MODULE_9__["default"](this.CiderHls, _types_loader__WEBPACK_IMPORTED_MODULE_6__["PlaylistLevelType"].AUDIO, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this));
} // Check if we have video initPTS
// If not we need to wait for it
@ -2198,9 +2182,7 @@ typeof window !== "undefined" &&
var chunkMeta = new _types_transmuxer__WEBPACK_IMPORTED_MODULE_10__["ChunkMetadata"](frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial);
transmuxer.push(payload, initSegmentData, audioCodec, "", frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS);
} else {
_utils_logger__WEBPACK_IMPORTED_MODULE_14__["logger"].log(
"Unknown video PTS for cc " + frag.cc + ", waiting for video PTS before demuxing audio frag " + frag.sn + " of [" + details.startSN + " ," + details.endSN + "],track " + trackId
);
_utils_logger__WEBPACK_IMPORTED_MODULE_14__["logger"].log("Unknown video PTS for cc " + frag.cc + ", waiting for video PTS before demuxing audio frag " + frag.sn + " of [" + details.startSN + " ," + details.endSN + "],track " + trackId);
var _this$waitingData = (this.waitingData = this.waitingData || {
frag: frag,
@ -3267,9 +3249,7 @@ typeof window !== "undefined" &&
state = this.state;
var currentTime = media ? media.currentTime : 0;
var bufferInfo = _utils_buffer_helper__WEBPACK_IMPORTED_MODULE_3__["BufferHelper"].bufferInfo(mediaBuffer || media, currentTime, config.maxBufferHole);
this.log(
"media seeking to " + (Object(_home_runner_work_CiderHls_js_CiderHls_js_src_polyfills_number__WEBPACK_IMPORTED_MODULE_0__["isFiniteNumber"])(currentTime) ? currentTime.toFixed(3) : currentTime) + ", state: " + state
);
this.log("media seeking to " + (Object(_home_runner_work_CiderHls_js_CiderHls_js_src_polyfills_number__WEBPACK_IMPORTED_MODULE_0__["isFiniteNumber"])(currentTime) ? currentTime.toFixed(3) : currentTime) + ", state: " + state);
if (state === State.ENDED) {
this.resetLoadingState();
@ -3619,19 +3599,7 @@ typeof window !== "undefined" &&
}
}
this.log(
"Loading fragment " +
frag.sn +
" cc: " +
frag.cc +
" " +
(details ? "of [" + details.startSN + "-" + details.endSN + "] " : "") +
(this.logPrefix === "[stream-controller]" ? "level" : "track") +
": " +
frag.level +
", target: " +
parseFloat(targetBufferTime.toFixed(3))
); // Don't update nextLoadPosition for fragments which are not buffered
this.log("Loading fragment " + frag.sn + " cc: " + frag.cc + " " + (details ? "of [" + details.startSN + "-" + details.endSN + "] " : "") + (this.logPrefix === "[stream-controller]" ? "level" : "track") + ": " + frag.level + ", target: " + parseFloat(targetBufferTime.toFixed(3))); // Don't update nextLoadPosition for fragments which are not buffered
if (Object(_home_runner_work_CiderHls_js_CiderHls_js_src_polyfills_number__WEBPACK_IMPORTED_MODULE_0__["isFiniteNumber"])(frag.sn) && !this.bitrateTest) {
this.nextLoadPosition = frag.start + frag.duration;
@ -4099,18 +4067,7 @@ typeof window !== "undefined" &&
if (firstLevelLoad || (!aligned && !slidingStart)) {
Object(_utils_discontinuities__WEBPACK_IMPORTED_MODULE_9__["alignStream"])(fragPrevious, lastLevel, details);
var alignedSlidingStart = details.fragments[0].start;
this.log(
"Live playlist sliding: " +
alignedSlidingStart.toFixed(2) +
" start-sn: " +
(previousDetails ? previousDetails.startSN : "na") +
"->" +
details.startSN +
" prev-sn: " +
(fragPrevious ? fragPrevious.sn : "na") +
" fragments: " +
length
);
this.log("Live playlist sliding: " + alignedSlidingStart.toFixed(2) + " start-sn: " + (previousDetails ? previousDetails.startSN : "na") + "->" + details.startSN + " prev-sn: " + (fragPrevious ? fragPrevious.sn : "na") + " fragments: " + length);
return alignedSlidingStart;
}
@ -5767,11 +5724,7 @@ typeof window !== "undefined" &&
ot: ot,
};
if (
ot === _types_cmcd__WEBPACK_IMPORTED_MODULE_1__["CMCDObjectType"].VIDEO ||
ot === _types_cmcd__WEBPACK_IMPORTED_MODULE_1__["CMCDObjectType"].AUDIO ||
ot == _types_cmcd__WEBPACK_IMPORTED_MODULE_1__["CMCDObjectType"].MUXED
) {
if (ot === _types_cmcd__WEBPACK_IMPORTED_MODULE_1__["CMCDObjectType"].VIDEO || ot === _types_cmcd__WEBPACK_IMPORTED_MODULE_1__["CMCDObjectType"].AUDIO || ot == _types_cmcd__WEBPACK_IMPORTED_MODULE_1__["CMCDObjectType"].MUXED) {
data.br = level.bitrate / 1000;
data.tb = _this.getTopBandwidth(ot);
data.bl = _this.getBufferLength(ot);
@ -5871,10 +5824,7 @@ typeof window !== "undefined" &&
// apply baseline data
_extends(data, this.createData());
var isVideo =
data.ot === _types_cmcd__WEBPACK_IMPORTED_MODULE_1__["CMCDObjectType"].INIT ||
data.ot === _types_cmcd__WEBPACK_IMPORTED_MODULE_1__["CMCDObjectType"].VIDEO ||
data.ot === _types_cmcd__WEBPACK_IMPORTED_MODULE_1__["CMCDObjectType"].MUXED;
var isVideo = data.ot === _types_cmcd__WEBPACK_IMPORTED_MODULE_1__["CMCDObjectType"].INIT || data.ot === _types_cmcd__WEBPACK_IMPORTED_MODULE_1__["CMCDObjectType"].VIDEO || data.ot === _types_cmcd__WEBPACK_IMPORTED_MODULE_1__["CMCDObjectType"].MUXED;
if (this.starved && isVideo) {
data.bs = true;
@ -8610,10 +8560,7 @@ typeof window !== "undefined" &&
levels = levels.filter(function (_ref2) {
var audioCodec = _ref2.audioCodec,
videoCodec = _ref2.videoCodec;
return (
(!audioCodec || Object(_utils_codecs__WEBPACK_IMPORTED_MODULE_3__["isCodecSupportedInMp4"])(audioCodec, "audio")) &&
(!videoCodec || Object(_utils_codecs__WEBPACK_IMPORTED_MODULE_3__["isCodecSupportedInMp4"])(videoCodec, "video"))
);
return (!audioCodec || Object(_utils_codecs__WEBPACK_IMPORTED_MODULE_3__["isCodecSupportedInMp4"])(audioCodec, "audio")) && (!videoCodec || Object(_utils_codecs__WEBPACK_IMPORTED_MODULE_3__["isCodecSupportedInMp4"])(videoCodec, "video"));
});
if (data.audioTracks) {
@ -9319,10 +9266,7 @@ typeof window !== "undefined" &&
ccOffset = oldFrag.cc - newFrag.cc;
}
if (
Object(_home_runner_work_CiderHls_js_CiderHls_js_src_polyfills_number__WEBPACK_IMPORTED_MODULE_0__["isFiniteNumber"])(oldFrag.startPTS) &&
Object(_home_runner_work_CiderHls_js_CiderHls_js_src_polyfills_number__WEBPACK_IMPORTED_MODULE_0__["isFiniteNumber"])(oldFrag.endPTS)
) {
if (Object(_home_runner_work_CiderHls_js_CiderHls_js_src_polyfills_number__WEBPACK_IMPORTED_MODULE_0__["isFiniteNumber"])(oldFrag.startPTS) && Object(_home_runner_work_CiderHls_js_CiderHls_js_src_polyfills_number__WEBPACK_IMPORTED_MODULE_0__["isFiniteNumber"])(oldFrag.endPTS)) {
newFrag.start = newFrag.startPTS = oldFrag.startPTS;
newFrag.startDTS = oldFrag.startDTS;
newFrag.appendedPTS = oldFrag.appendedPTS;
@ -9902,10 +9846,7 @@ typeof window !== "undefined" &&
} // We want to load the key if we're dealing with an identity key, because we will decrypt
// this content using the key we fetch. Other keys will be handled by the DRM CDM via EME.
if (
((_frag$decryptdata = frag.decryptdata) === null || _frag$decryptdata === void 0 ? void 0 : _frag$decryptdata.keyFormat) === "identity" &&
!((_frag$decryptdata2 = frag.decryptdata) !== null && _frag$decryptdata2 !== void 0 && _frag$decryptdata2.key)
) {
if (((_frag$decryptdata = frag.decryptdata) === null || _frag$decryptdata === void 0 ? void 0 : _frag$decryptdata.keyFormat) === "identity" && !((_frag$decryptdata2 = frag.decryptdata) !== null && _frag$decryptdata2 !== void 0 && _frag$decryptdata2.key)) {
this.loadKey(frag, levelDetails);
} else {
this.loadFragment(frag, levelDetails, targetBufferTime);
@ -10257,13 +10198,7 @@ typeof window !== "undefined" &&
// this.log(`Transmuxing ${frag.sn} of [${details.startSN} ,${details.endSN}],level ${frag.level}, cc ${frag.cc}`);
var transmuxer = (this.transmuxer =
this.transmuxer ||
new _demux_transmuxer_interface__WEBPACK_IMPORTED_MODULE_8__["default"](
this.CiderHls,
_types_loader__WEBPACK_IMPORTED_MODULE_6__["PlaylistLevelType"].MAIN,
this._handleTransmuxComplete.bind(this),
this._handleTransmuxerFlush.bind(this)
));
this.transmuxer || new _demux_transmuxer_interface__WEBPACK_IMPORTED_MODULE_8__["default"](this.CiderHls, _types_loader__WEBPACK_IMPORTED_MODULE_6__["PlaylistLevelType"].MAIN, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this)));
var partIndex = part ? part.index : -1;
var partial = partIndex !== -1;
var chunkMeta = new _types_transmuxer__WEBPACK_IMPORTED_MODULE_9__["ChunkMetadata"](frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial);
@ -14779,8 +14714,8 @@ typeof window !== "undefined" &&
*/
var chromeVersion = null;
var BitratesMap = [
32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 32, 48, 56, 64, 80, 96, 112,
128, 144, 160, 176, 192, 224, 256, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,
32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 8, 16, 24, 32, 40, 48, 56,
64, 80, 96, 112, 128, 144, 160,
];
var SamplingRateMap = [44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000];
var SamplesCoefficients = [
@ -18354,9 +18289,7 @@ typeof window !== "undefined" &&
var _sourceBuffer$prototy;
var sourceBuffer = getSourceBuffer();
return (
typeof (sourceBuffer === null || sourceBuffer === void 0 ? void 0 : (_sourceBuffer$prototy = sourceBuffer.prototype) === null || _sourceBuffer$prototy === void 0 ? void 0 : _sourceBuffer$prototy.changeType) === "function"
);
return typeof (sourceBuffer === null || sourceBuffer === void 0 ? void 0 : (_sourceBuffer$prototy = sourceBuffer.prototype) === null || _sourceBuffer$prototy === void 0 ? void 0 : _sourceBuffer$prototy.changeType) === "function";
}
/***/
@ -18743,10 +18676,7 @@ typeof window !== "undefined" &&
var start = segment.byteRangeStartOffset;
var end = segment.byteRangeEndOffset;
if (
Object(_home_runner_work_CiderHls_js_CiderHls_js_src_polyfills_number__WEBPACK_IMPORTED_MODULE_0__["isFiniteNumber"])(start) &&
Object(_home_runner_work_CiderHls_js_CiderHls_js_src_polyfills_number__WEBPACK_IMPORTED_MODULE_0__["isFiniteNumber"])(end)
) {
if (Object(_home_runner_work_CiderHls_js_CiderHls_js_src_polyfills_number__WEBPACK_IMPORTED_MODULE_0__["isFiniteNumber"])(start) && Object(_home_runner_work_CiderHls_js_CiderHls_js_src_polyfills_number__WEBPACK_IMPORTED_MODULE_0__["isFiniteNumber"])(end)) {
loaderContext.rangeStart = start;
loaderContext.rangeEnd = end;
}
@ -18854,12 +18784,7 @@ typeof window !== "undefined" &&
this._url = null;
this.baseurl = void 0;
this.relurl = void 0;
this.elementaryStreams =
((_this$elementaryStrea = {}),
(_this$elementaryStrea[ElementaryStreamTypes.AUDIO] = null),
(_this$elementaryStrea[ElementaryStreamTypes.VIDEO] = null),
(_this$elementaryStrea[ElementaryStreamTypes.AUDIOVIDEO] = null),
_this$elementaryStrea);
this.elementaryStreams = ((_this$elementaryStrea = {}), (_this$elementaryStrea[ElementaryStreamTypes.AUDIO] = null), (_this$elementaryStrea[ElementaryStreamTypes.VIDEO] = null), (_this$elementaryStrea[ElementaryStreamTypes.AUDIOVIDEO] = null), _this$elementaryStrea);
this.baseurl = baseurl;
} // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array
@ -19730,11 +19655,7 @@ typeof window !== "undefined" &&
function isMP4Url(url) {
var _URLToolkit$parseURL$, _URLToolkit$parseURL;
return MP4_REGEX_SUFFIX.test(
(_URLToolkit$parseURL$ = (_URLToolkit$parseURL = url_toolkit__WEBPACK_IMPORTED_MODULE_1__["parseURL"](url)) === null || _URLToolkit$parseURL === void 0 ? void 0 : _URLToolkit$parseURL.path) != null
? _URLToolkit$parseURL$
: ""
);
return MP4_REGEX_SUFFIX.test((_URLToolkit$parseURL$ = (_URLToolkit$parseURL = url_toolkit__WEBPACK_IMPORTED_MODULE_1__["parseURL"](url)) === null || _URLToolkit$parseURL === void 0 ? void 0 : _URLToolkit$parseURL.path) != null ? _URLToolkit$parseURL$ : "");
}
var M3U8Parser = /*#__PURE__*/ (function () {
@ -20819,9 +20740,7 @@ typeof window !== "undefined" &&
timeout = false;
}
_utils_logger__WEBPACK_IMPORTED_MODULE_3__["logger"].warn(
"[playlist-loader]: A network " + (timeout ? "timeout" : "error") + " occurred while loading " + context.type + " level: " + context.level + " id: " + context.id + ' group-id: "' + context.groupId + '"'
);
_utils_logger__WEBPACK_IMPORTED_MODULE_3__["logger"].warn("[playlist-loader]: A network " + (timeout ? "timeout" : "error") + " occurred while loading " + context.type + " level: " + context.level + " id: " + context.id + ' group-id: "' + context.groupId + '"');
var details = _errors__WEBPACK_IMPORTED_MODULE_2__["ErrorDetails"].UNKNOWN;
var fatal = false;
var loader = this.getInternalLoader(context);
@ -21004,20 +20923,20 @@ typeof window !== "undefined" &&
if (channelCount === 1) {
// ffmpeg -y -f lavfi -i "aevalsrc=0:d=0.05" -c:a libfdk_aac -profile:a aac_he -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac
return new Uint8Array([
0x1, 0x40, 0x22, 0x80, 0xa3, 0x4e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x1c, 0x6, 0xf1, 0xc1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e,
0x1, 0x40, 0x22, 0x80, 0xa3, 0x4e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x1c, 0x6, 0xf1, 0xc1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e,
]);
} else if (channelCount === 2) {
// ffmpeg -y -f lavfi -i "aevalsrc=0|0:d=0.05" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac
return new Uint8Array([
0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e,
0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e,
]);
} else if (channelCount === 3) {
// ffmpeg -y -f lavfi -i "aevalsrc=0|0|0:d=0.05" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac
return new Uint8Array([
0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e,
0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e,
]);
}
@ -22334,13 +22253,7 @@ typeof window !== "undefined" &&
initSegment = this.generateIS(audioTrack, videoTrack, timeOffset);
}
audio = this.remuxAudio(
audioTrack,
audioTimeOffset,
this.isAudioContiguous,
accurateTimeOffset,
hasVideo || enoughVideoSamples || playlistType === _types_loader__WEBPACK_IMPORTED_MODULE_6__["PlaylistLevelType"].AUDIO ? videoTimeOffset : undefined
);
audio = this.remuxAudio(audioTrack, audioTimeOffset, this.isAudioContiguous, accurateTimeOffset, hasVideo || enoughVideoSamples || playlistType === _types_loader__WEBPACK_IMPORTED_MODULE_6__["PlaylistLevelType"].AUDIO ? videoTimeOffset : undefined);
if (enoughVideoSamples) {
var audioTrackLength = audio ? audio.endPTS - audio.startPTS : 0; // if initSegment was generated without video samples, regenerate it again
@ -22531,9 +22444,7 @@ typeof window !== "undefined" &&
if (ptsDtsShift < averageSampleDuration * -2) {
// Fix for "CNN special report, with CC" in test-streams (including Safari browser)
// With large PTS < DTS errors such as this, we want to correct CTS while maintaining increasing DTS values
_utils_logger__WEBPACK_IMPORTED_MODULE_5__["logger"].warn(
"PTS < DTS detected in video samples, offsetting DTS from PTS by " + Object(_utils_timescale_conversion__WEBPACK_IMPORTED_MODULE_7__["toMsFromMpegTsClock"])(-averageSampleDuration, true) + " ms"
);
_utils_logger__WEBPACK_IMPORTED_MODULE_5__["logger"].warn("PTS < DTS detected in video samples, offsetting DTS from PTS by " + Object(_utils_timescale_conversion__WEBPACK_IMPORTED_MODULE_7__["toMsFromMpegTsClock"])(-averageSampleDuration, true) + " ms");
var lastDts = ptsDtsShift;
for (var _i = 0; _i < nbSamples; _i++) {
@ -22543,9 +22454,7 @@ typeof window !== "undefined" &&
} else {
// Fix for "Custom IV with bad PTS DTS" in test-streams
// With smaller PTS < DTS errors we can simply move all DTS back. This increases CTS without causing buffer gaps or decode errors in Safari
_utils_logger__WEBPACK_IMPORTED_MODULE_5__["logger"].warn(
"PTS < DTS detected in video samples, shifting DTS by " + Object(_utils_timescale_conversion__WEBPACK_IMPORTED_MODULE_7__["toMsFromMpegTsClock"])(ptsDtsShift, true) + " ms to overcome this issue"
);
_utils_logger__WEBPACK_IMPORTED_MODULE_5__["logger"].warn("PTS < DTS detected in video samples, shifting DTS by " + Object(_utils_timescale_conversion__WEBPACK_IMPORTED_MODULE_7__["toMsFromMpegTsClock"])(ptsDtsShift, true) + " ms to overcome this issue");
for (var _i2 = 0; _i2 < nbSamples; _i2++) {
inputSamples[_i2].dts = inputSamples[_i2].dts + ptsDtsShift;
@ -22563,13 +22472,9 @@ typeof window !== "undefined" &&
if (foundHole || foundOverlap) {
if (foundHole) {
_utils_logger__WEBPACK_IMPORTED_MODULE_5__["logger"].warn(
"AVC: " + Object(_utils_timescale_conversion__WEBPACK_IMPORTED_MODULE_7__["toMsFromMpegTsClock"])(delta, true) + " ms (" + delta + "dts) hole between fragments detected, filling it"
);
_utils_logger__WEBPACK_IMPORTED_MODULE_5__["logger"].warn("AVC: " + Object(_utils_timescale_conversion__WEBPACK_IMPORTED_MODULE_7__["toMsFromMpegTsClock"])(delta, true) + " ms (" + delta + "dts) hole between fragments detected, filling it");
} else {
_utils_logger__WEBPACK_IMPORTED_MODULE_5__["logger"].warn(
"AVC: " + Object(_utils_timescale_conversion__WEBPACK_IMPORTED_MODULE_7__["toMsFromMpegTsClock"])(-delta, true) + " ms (" + delta + "dts) overlapping between fragments detected"
);
_utils_logger__WEBPACK_IMPORTED_MODULE_5__["logger"].warn("AVC: " + Object(_utils_timescale_conversion__WEBPACK_IMPORTED_MODULE_7__["toMsFromMpegTsClock"])(-delta, true) + " ms (" + delta + "dts) overlapping between fragments detected");
}
firstDTS = nextAvcDts;
@ -22682,9 +22587,7 @@ typeof window !== "undefined" &&
mp4SampleDuration = lastFrameDuration;
}
_utils_logger__WEBPACK_IMPORTED_MODULE_5__["logger"].log(
"[mp4-remuxer]: It is approximately " + deltaToFrameEnd / 90 + " ms to the next segment; using duration " + mp4SampleDuration / 90 + " ms for the last video frame."
);
_utils_logger__WEBPACK_IMPORTED_MODULE_5__["logger"].log("[mp4-remuxer]: It is approximately " + deltaToFrameEnd / 90 + " ms to the next segment; using duration " + mp4SampleDuration / 90 + " ms for the last video frame.");
} else {
mp4SampleDuration = lastFrameDuration;
}
@ -22759,10 +22662,7 @@ typeof window !== "undefined" &&
var timeOffsetMpegTS = timeOffset * inputTimeScale;
this.isAudioContiguous = contiguous =
contiguous ||
(inputSamples.length &&
nextAudioPts > 0 &&
((accurateTimeOffset && Math.abs(timeOffsetMpegTS - nextAudioPts) < 9000) || Math.abs(normalizePts(inputSamples[0].pts - initPTS, timeOffsetMpegTS) - nextAudioPts) < 20 * inputSampleDuration)); // compute normalized PTS
contiguous || (inputSamples.length && nextAudioPts > 0 && ((accurateTimeOffset && Math.abs(timeOffsetMpegTS - nextAudioPts) < 9000) || Math.abs(normalizePts(inputSamples[0].pts - initPTS, timeOffsetMpegTS) - nextAudioPts) < 20 * inputSampleDuration)); // compute normalized PTS
inputSamples.forEach(function (sample) {
sample.pts = normalizePts(sample.pts - initPTS, timeOffsetMpegTS);
@ -22833,9 +22733,7 @@ typeof window !== "undefined" &&
this.nextAudioPts = nextAudioPts = nextPts;
}
_utils_logger__WEBPACK_IMPORTED_MODULE_5__["logger"].warn(
"[mp4-remuxer]: Injecting " + missing + " audio frame @ " + (nextPts / inputTimeScale).toFixed(3) + "s due to " + Math.round((1000 * delta) / inputTimeScale) + " ms gap."
);
_utils_logger__WEBPACK_IMPORTED_MODULE_5__["logger"].warn("[mp4-remuxer]: Injecting " + missing + " audio frame @ " + (nextPts / inputTimeScale).toFixed(3) + "s due to " + Math.round((1000 * delta) / inputTimeScale) + " ms gap.");
for (var j = 0; j < missing; j++) {
var newStamp = Math.max(nextPts, 0);

View file

@ -334,6 +334,15 @@
width: 42px;
height: 42px;
flex: 0 0 auto;
&.circle {
border-radius: 50%;
.mediaitem-artwork {
border-radius: 50%;
}
img {
border-radius: 50%;
}
}
}
&:hover {
@ -349,6 +358,11 @@
color: #eee;
}
&.hintactive {
background: var(--keyColor);
border-radius: 6px;
}
.queue-info {
justify-content: center;
display: flex;
@ -392,6 +406,38 @@
}
}
.search-hints .cd-queue-item {
&:hover {
background: var(--selected);
border-radius: 6px;
.circular-play-button {
opacity: 1;
}
}
&:active {
background: var(--selected-click);
color: #eee;
border-radius: 6px;
}
}
/* Circle Play Button */
.circular-play-button {
position: relative;
opacity: 0;
top: -34px;
z-index: 5;
left: 8px;
align-items: center;
background: rgba(100, 100, 100, 0.5);
border: none;
cursor: pointer;
border-radius: 100%;
height: 26px;
box-shadow: var(--ciderShadow-Generic);
}
/* horizontal media scroller */
.cd-hmedia-scroller {
&::-webkit-scrollbar-thumb {
@ -1842,6 +1888,12 @@ input[type="checkbox"][switch]:checked:active::before {
background-position: center;
}
.playback-button.stop {
background-image: url("./assets/cider-icons/stop.svg");
background-size: 38px;
background-position: center;
}
.playback-button.play {
background-image: url("./assets/cider-icons/play.svg");
background-size: 38px;

View file

@ -58,6 +58,10 @@ const Events = {
try {
app.mk._services.mediaItemPlayback._currentPlayer.destroy();
} catch (e) {}
try {
this.radiohls.destroy();
this.radiohls = null;
} catch (_) {}
try {
let searchInt = setInterval(function () {
if (document.getElementById("apple-music-player")) {

View file

@ -20,11 +20,13 @@ const app = new Vue({
pluginMenuEntries: [],
lz: ipcRenderer.sendSync("get-i18n", "en_US"),
lzListing: ipcRenderer.sendSync("get-i18n-listing"),
radiohls: null,
search: {
term: "",
cursor: -1,
hints: [],
showHints: false,
showSearchView: false,
results: {},
resultsSocial: {},
resultsLibrary: {},
@ -44,11 +46,7 @@ const app = new Vue({
browsepage: [],
listennow: [],
madeforyou: [],
radio: {
personal: {},
recent: {},
amlive: {},
},
radio: [],
mklang: "en",
webview: {
url: "",
@ -159,6 +157,7 @@ const app = new Vue({
miniTmpY: "",
tmpVar: [],
notification: false,
hintscontext: false,
chrome: {
sidebarCollapsed: false,
nativeControls: false,
@ -247,6 +246,8 @@ const app = new Vue({
idleTimer: null,
idleState: false,
appVisible: true,
currentAirPlayCodeID: "",
airplayTrys: [],
},
watch: {
cfg: {
@ -256,6 +257,12 @@ const app = new Vue({
},
deep: true,
},
"cfg.connectivity.discord_rpc.enabled"(newValue) {
ipcRenderer.send("discordrpc:reload", newValue);
},
"mk.privateEnabled"(newValue) {
ipcRenderer.send("onPrivacyModeChange", newValue);
},
page: () => {
document.getElementById("app-content").scrollTo(0, 0);
app.resetState();
@ -287,6 +294,12 @@ const app = new Vue({
},
async oobeInit() {
this.appMode = "oobe";
for (const [k, v] of Object.entries(ipcRenderer.sendSync("get-i18n-listing"))) {
if (v.code === navigator.language.replace("-", "_")) {
this.cfg.general.language = v.code;
break;
}
}
this.setLz(this.cfg.general.language);
this.setLzManual();
clearTimeout(this.hangtimer);
@ -569,7 +582,24 @@ const app = new Vue({
window.location.hash = `#charts/top`;
} else {
const id = url.split("id=")[1];
if (id != null) {
window.location.hash = `#groupings/${id}`;
} else {
const params = new Proxy(new URLSearchParams(new URL(url).search), {
get: (searchParams, prop) => searchParams.get(prop),
});
let id = params.fcId;
app
.getTypeFromID("room", id, false, {
platform: "web",
extend: "editorialArtwork,uber,lockupStyle",
})
.then(() => {
let kind = "multiroom";
window.location.hash = `${kind}/${id}`;
document.querySelector("#app-content").scrollTop = 0;
});
}
}
},
navigateForward() {
@ -633,9 +663,46 @@ const app = new Vue({
this.modals.addToPlaylist = false;
app.newPlaylist(app.getLz("term.newPlaylist"), pl_items);
},
async isSongInPlaylist(song_ids, playlist_id) {
let isInPlaylist = false;
const playlistTracks = (
await app.mk.api.v3.music(`/v1/me/library/playlists/${playlist_id}/tracks`, {
platform: "web",
l: app.mklang,
})
).data?.data;
playlistTracks.forEach((track) => {
if (song_ids.includes(track.id)) {
isInPlaylist = true;
}
});
return isInPlaylist;
},
addToPlaylist(pid, pitems) {
app.mk.api.v3
.music(
`/v1/me/library/playlists/${pid}/tracks`,
{},
{
fetchOptions: {
method: "POST",
body: JSON.stringify({
data: pitems,
}),
},
}
)
.then(() => {
if (app.page === "playlist_" + pid) {
app.getPlaylistFromID(app.showingPlaylist.id, true);
}
});
},
async addSelectedToPlaylist(playlist_id) {
let self = this;
let pl_items = [];
const song_ids = [];
for (let i = 0; i < self.selectedMediaItems.length; i++) {
if (self.selectedMediaItems[i].kind == "song" || self.selectedMediaItems[i].kind == "songs") {
self.selectedMediaItems[i].kind = "songs";
@ -643,6 +710,7 @@ const app = new Vue({
id: self.selectedMediaItems[i].id,
type: self.selectedMediaItems[i].kind,
});
song_ids.push(self.selectedMediaItems[i].id);
} else if ((self.selectedMediaItems[i].kind == "album" || self.selectedMediaItems[i].kind == "albums") && self.selectedMediaItems[i].isLibrary != true) {
self.selectedMediaItems[i].kind = "albums";
let res = await self.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/albums/${self.selectedMediaItems[i].id}/tracks`);
@ -650,12 +718,14 @@ const app = new Vue({
return { id: i.id, type: i.type };
});
pl_items = pl_items.concat(ids);
song_ids.push(...ids.map((id) => id.id));
} else if (self.selectedMediaItems[i].kind == "library-song" || self.selectedMediaItems[i].kind == "library-songs") {
self.selectedMediaItems[i].kind = "library-songs";
pl_items.push({
id: self.selectedMediaItems[i].id,
type: self.selectedMediaItems[i].kind,
});
song_ids.push(self.selectedMediaItems[i].id);
} else if (self.selectedMediaItems[i].kind == "library-album" || self.selectedMediaItems[i].kind == "library-albums" || (self.selectedMediaItems[i].kind == "album" && self.selectedMediaItems[i].isLibrary == true)) {
self.selectedMediaItems[i].kind = "library-albums";
let res = await self.mk.api.v3.music(`/v1/me/library/albums/${self.selectedMediaItems[i].id}/tracks`);
@ -663,32 +733,26 @@ const app = new Vue({
return { id: i.id, type: i.type };
});
pl_items = pl_items.concat(ids);
song_ids.push(...ids.map((id) => id.id));
} else {
pl_items.push({
id: self.selectedMediaItems[i].id,
type: self.selectedMediaItems[i].kind,
});
song_ids.push(self.selectedMediaItems[i].id);
}
}
this.modals.addToPlaylist = false;
await app.mk.api.v3
.music(
`/v1/me/library/playlists/${playlist_id}/tracks`,
{},
{
fetchOptions: {
method: "POST",
body: JSON.stringify({
data: pl_items,
}),
},
}
)
.then(() => {
if (this.page == "playlist_" + this.showingPlaylist.id) {
this.getPlaylistFromID(this.showingPlaylist.id, true);
if (await this.isSongInPlaylist(song_ids, playlist_id)) {
app.confirm(app.getLz("action.addToPlaylist.duplicate"), (result) => {
if (result === true) {
app.addToPlaylist(playlist_id, pl_items);
}
});
} else {
app.addToPlaylist(playlist_id, pl_items);
}
},
async init() {
let self = this;
@ -773,6 +837,8 @@ const app = new Vue({
this.mk.volume = -1;
}
// Restore mk
// load cached library
let librarySongs = await CiderCache.getCache("library-songs");
let libraryAlbums = await CiderCache.getCache("library-albums");
@ -995,6 +1061,11 @@ const app = new Vue({
return;
} // EVIL EMPTY OBJECTS BE GONE
try {
this.radiohls.destroy();
this.radiohls = null;
} catch (_) {}
try {
if ((MusicKit.getInstance().nowPlayingItem["type"] ?? "").includes("ideo")) {
setTimeout(() => {
@ -1141,8 +1212,6 @@ const app = new Vue({
if (this.cfg.general.themeUpdateNotification && !this.isDev) {
this.checkForThemeUpdates();
}
ipcRenderer.invoke("scanLibrary");
},
setWindowScaleFactor() {
let scale = (((window.devicePixelRatio * window.innerWidth) / 1280) * window.innerHeight) / 720;
@ -1181,7 +1250,7 @@ const app = new Vue({
const notify = notyf.open({
className: "notyf-info",
type: "info",
message: `[Themes] ${theme.name} has an update available.`,
message: app.stringTemplateParser(app.getLz("settings.notyf.visual.theme.updateAvailable"), { theme: theme.name }),
});
notify.on("click", () => {
app.openSettingsPage("github-themes");
@ -1457,15 +1526,15 @@ const app = new Vue({
const cachedTrackMapping = await CiderCache.getCache("library-playlists-tracks");
if (cachedPlaylist) {
console.debug("using cached playlists");
console.debug("[CiderCache] Using cached playlist");
this.playlists.listing = cachedPlaylist;
self.sortPlaylists();
} else {
console.debug("playlist has no cache");
console.debug("[CiderCache] Playlist has no cache");
}
if (cachedTrackMapping) {
console.debug("using cached track mapping");
console.debug("[CiderCache] Using cached track mapping");
this.playlists.trackMapping = cachedTrackMapping;
}
if (localOnly) {
@ -1473,7 +1542,7 @@ const app = new Vue({
}
}
this.library.backgroundNotification.message = "Building playlist cache...";
this.library.backgroundNotification.message = app.getLz("notification.buildingPlaylistCache");
this.library.backgroundNotification.show = true;
async function deepScan(parent = "p.playlistsroot") {
@ -1895,10 +1964,31 @@ const app = new Vue({
async getSearchHints() {
if (this.search.term == "") {
this.search.hints = [];
this.search.showHints = true;
this.search.showSearchView = false;
return;
}
let hints = await (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search/hints?term=${this.search.term}`)).data.results;
this.search.hints = hints ? hints.terms : [];
let hints = await (
await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search/suggestions?term=${encodeURIComponent(this.search.term)}`, {
"fields[albums]": "artwork,name,playParams,url,artistName,id",
"fields[artists]": "url,name,artwork,id",
"fields[songs]": "artwork,name,playParams,url,artistName,id",
kinds: "terms,topResults",
l: this.mklang,
"limit[results:terms]": 5,
"limit[results:topResults]": 5,
"omit[resource]": "autos",
platform: "web",
types: "activities,albums,artists,editorial-items,music-movies,playlists,record-labels,songs,stations",
})
).data.results;
let shints = hints ? hints.suggestions : [];
for (let item in shints) {
if ((shints[item]?.displayTerm ?? "").includes("?fields[")) {
shints[item].displayTerm = shints[item].searchTerm = shints[item].displayTerm.split("?fields[")[0];
}
}
this.search.hints = shints;
},
getSongProgress() {
if (this.playerLCD.userInteraction) {
@ -1916,6 +2006,7 @@ const app = new Vue({
* @memberOf app
*/
convertTime(seconds, format = "short") {
if (app.mk?.nowPlayingItem?.type === "radioStation") return;
if (isNaN(seconds) || seconds === Infinity) {
seconds = 0;
}
@ -1996,26 +2087,36 @@ const app = new Vue({
}
if (kind.toString().includes("apple-curator")) {
kind = "appleCurator";
app.getTypeFromID("appleCurator", id, false, {
app
.getTypeFromID("appleCurator", id, false, {
platform: "web",
include: "grouping,playlists",
extend: "editorialArtwork",
"art[url]": "f",
});
})
.then(() => {
kind = "appleCurator";
window.location.hash = `${kind}/${id}`;
document.querySelector("#app-content").scrollTop = 0;
});
} else if (kind == "editorial-elements" || kind == "editorial-items") {
console.debug(item);
if (item.relationships?.contents?.data != null && item.relationships?.contents?.data.length > 0) {
this.routeView(item.relationships.contents.data[0]);
} else if (item.attributes?.link?.url != null) {
if (item.attributes.link.url.includes("viewMultiRoom")) {
if (item.attributes.link.url.includes("viewMultiRoom") || item.attributes.link.url.includes("/collection/")) {
const params = new Proxy(new URLSearchParams(new URL(item.attributes.link.url).search), {
get: (searchParams, prop) => searchParams.get(prop),
});
id = params.fcId;
kind = "multiroom";
if (item.attributes.link.url.includes("viewMultiRoom")) {
kind = "multiroom";
} else {
kind = "room";
}
app
.getTypeFromID("multiroom", id, false, {
.getTypeFromID(kind, id, false, {
platform: "web",
extend: "editorialArtwork,uber,lockupStyle",
})
@ -2116,6 +2217,17 @@ const app = new Vue({
}
// app.getTypeFromID((kind), (id), (isLibrary), params);
} else if (kind.toString().includes("song")) {
const albumUrl = new Promise(async (resolve, reject) => {
resolve(await MusicKitInterop.fetchSongRelationships({ id: id, relationship: "album" }));
});
albumUrl.then((data) => {
if (data && data.type === "albums" && data.id) {
window.location.hash = `album/${data.id}${isLibrary ? "/" + isLibrary : ""}`;
} else {
app.playMediaItemById(id, kind, isLibrary, item.attributes.url ?? "");
}
});
} else {
app.playMediaItemById(id, kind, isLibrary, item.attributes.url ?? "");
}
@ -2184,6 +2296,11 @@ const app = new Vue({
artistId = item.relationships.artists.data[0].id;
}
}
if (item.relationships.albums && item.relationships.albums.data.length > 0) {
if (item.relationships.albums.data[0].attributes.artistUrl) {
artistId = item.relationships.albums.data[0].attributes.artistUrl.split("/").pop();
}
}
if (artistId == "") {
const url = item.relationships.catalog.data[0].attributes.artistUrl;
artistId = url.substring(url.lastIndexOf("/") + 1);
@ -2324,7 +2441,7 @@ const app = new Vue({
} finally {
if (kind == "appleCurator") {
app.appleCurator = a.data.data[0];
} else if (kind == "multiroom") {
} else if (kind == "multiroom" || kind == "room") {
app.multiroom = a.data.data[0];
} else {
this.getPlaylistContinuous(a, true);
@ -2333,7 +2450,7 @@ const app = new Vue({
} finally {
if (kind == "appleCurator") {
app.appleCurator = a.data.data[0];
} else if (kind == "multiroom") {
} else if (kind == "multiroom" || kind == "room") {
app.multiroom = a.data.data[0];
} else {
this.getPlaylistContinuous(a, true);
@ -2344,28 +2461,32 @@ const app = new Vue({
let self = this;
let prefs = this.cfg.libraryPrefs.songs;
const albumAdded = {};
for (const listing of self.library?.albums?.listing ?? []) {
albumAdded[listing.id] = listing.attributes?.dateAdded;
}
let startTime = new Date().getTime();
function sortSongs() {
// sort this.library.songs.displayListing by song.attributes[self.library.songs.sorting] in descending or ascending order based on alphabetical order and numeric order
// check if song.attributes[self.library.songs.sorting] is a number and if so, sort by number if not, sort by alphabetical order ignoring case
self.library.songs.displayListing.sort((a, b) => {
let aa = a.attributes[prefs.sort];
let bb = b.attributes[prefs.sort];
if (prefs.sort == "genre") {
if (prefs.sort === "genre") {
aa = a.attributes.genreNames[0];
bb = b.attributes.genreNames[0];
} else if (prefs.sort == "dateAdded") {
let albumida = a.relationships?.albums?.data[0]?.id;
let albumidb = b.relationships?.albums?.data[0]?.id;
aa = startTime - new Date(albumAdded[albumida] ?? "1970-01-01T00:01:01Z").getTime();
bb = startTime - new Date(albumAdded[albumidb] ?? "1970-01-01T00:01:01Z").getTime();
} else if (prefs.sort === "dateAdded") {
aa = a.relationships?.albums?.data[0]?.attributes?.dateAdded;
bb = b.relationships?.albums?.data[0]?.attributes?.dateAdded;
} else if (prefs.sort === "artistName") {
if (a.relationships?.artists?.data[0]?.id === b.relationships?.artists?.data[0]?.id) {
aa = a.attributes.albumName;
bb = b.attributes.albumName;
}
if (a.relationships?.albums?.data[0]?.id === b.relationships?.albums?.data[0]?.id) {
aa = a.attributes.trackNumber;
bb = b.attributes.trackNumber;
}
} else if (prefs.sort === "albumName") {
if (a.relationships?.albums?.data[0]?.id === b.relationships?.albums?.data[0]?.id) {
aa = a.attributes.trackNumber;
bb = b.attributes.trackNumber;
}
}
if (aa == null) {
aa = "";
@ -2373,13 +2494,13 @@ const app = new Vue({
if (bb == null) {
bb = "";
}
if (prefs.sortOrder == "asc") {
if (prefs.sortOrder === "asc") {
if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) {
return aa - bb;
} else {
return aa.toString().toLowerCase().localeCompare(bb.toString().toLowerCase());
}
} else if (prefs.sortOrder == "desc") {
} else if (prefs.sortOrder === "desc") {
if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) {
return bb - aa;
} else {
@ -2576,7 +2697,7 @@ const app = new Vue({
}
let truemethod = !method.endsWith("s") ? method + "s" : method;
try {
if (method.includes(`multiroom`)) {
if (method.includes(`room`)) {
return await this.mk.api.v3.music(`v1/editorial/${app.mk.storefrontId}/${truemethod}/${term.toString()}`, params, params2);
} else if (library) {
return await this.mk.api.v3.music(`v1/me/library/${truemethod}/${term.toString()}`, params, params2);
@ -2972,6 +3093,38 @@ const app = new Vue({
this.getListenNow(attempt + 1);
}
},
async getRadioPage(attempt = 0) {
if (this.radio.timestamp > Date.now() - 120000) {
return;
}
if (attempt > 3) {
return;
}
try {
app.mk.api.v3
.music(`/v1/editorial/${app.mk.storefrontId}/groupings`, {
platform: "web",
name: "radio",
"omit[resource:artists]": "relationships",
"include[albums]": "artists",
"include[songs]": "artists",
"include[music-videos]": "artists",
extend: "editorialArtwork,artistUrl",
"fields[artists]": "name,url,artwork,editorialArtwork,genreNames,editorialNotes",
"art[url]": "f",
l: app.mklang,
})
.then((radio) => {
app.radio = radio.data.data[0];
console.debug(app.radio);
});
this.radio.timestamp = Date.now();
} catch (e) {
console.log(e);
this.getRadioPage(attempt + 1);
}
},
async getBrowsePage(attempt = 0) {
if (this.browsepage.timestamp > Date.now() - 120000) {
return;
@ -2990,7 +3143,7 @@ const app = new Vue({
extend: "editorialArtwork,artistUrl",
"fields[artists]": "name,url,artwork,editorialArtwork,genreNames,editorialNotes",
"art[url]": "f",
l: this.mklang,
l: app.mklang,
});
this.browsepage = browse.data.data[0];
this.browsepage.timestamp = Date.now();
@ -3005,9 +3158,7 @@ const app = new Vue({
return;
}
try {
let mfu = await app.mk.api.v3.music(
"/v1/me/library/playlists?platform=web&extend=editorialVideo&fields%5Bplaylists%5D=lastModifiedDate&filter%5Bfeatured%5D=made-for-you&include%5Blibrary-playlists%5D=catalog&fields%5Blibrary-playlists%5D=artwork%2Cname%2CplayParams%2CdateAdded"
);
let mfu = await app.mk.api.v3.music("/v1/me/library/playlists?platform=web&extend=editorialVideo&fields%5Bplaylists%5D=lastModifiedDate&filter%5Bfeatured%5D=made-for-you&include%5Blibrary-playlists%5D=catalog&fields%5Blibrary-playlists%5D=artwork%2Cname%2CplayParams%2CdateAdded");
this.madeforyou = mfu.data;
} catch (e) {
console.log(e);
@ -3187,10 +3338,7 @@ const app = new Vue({
let id,
songLang = "";
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"] ?? "";
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"];
@ -3880,15 +4028,22 @@ const app = new Vue({
if (e.keyCode == "40") {
if (this.search.hints.length - 1 < this.search.cursor + 1) return;
this.search.cursor++;
this.search.term = this.search.hints[this.search.cursor];
let item = this.search.hints[this.search.cursor];
this.search.term = item.content ? item.content?.attributes?.name ?? "" : item.displayTerm;
} else if (e.keyCode == "38") {
if (this.search.cursor == 0) return;
this.search.cursor--;
this.search.term = this.search.hints[this.search.cursor];
let item = this.search.hints[this.search.cursor];
this.search.term = item.content ? item.content?.attributes?.name ?? "" : item.displayTerm;
}
},
async searchQuery(term = this.search.term) {
let self = this;
if (typeof term === "object") {
this.routeView(term);
this.search.term = "";
return;
}
if (term == "") {
return;
}
@ -4016,6 +4171,7 @@ const app = new Vue({
}
},
getMediaItemArtwork(url, height = 64, width) {
try {
if (typeof url == "undefined" || url == "") {
return "./assets/MissingArtwork.svg";
}
@ -4023,7 +4179,7 @@ const app = new Vue({
if (width) {
width = parseInt(width * window.devicePixelRatio);
}
let newurl = `${url
let newurl = `${(url ?? "")
.replace("{w}", width ?? height)
.replace("{h}", height)
.replace("{f}", "webp")
@ -4033,6 +4189,10 @@ const app = new Vue({
newurl = newurl.replace("900x516cc", "900x516sr").replace("900x516bb", "900x516sr");
}
return newurl;
} catch (e) {
console.log(url);
return "./assets/MissingArtwork.svg";
}
},
_rgbToRgb(rgb = [0, 0, 0]) {
// if rgb
@ -4091,13 +4251,7 @@ const app = new Vue({
}
this.currentArtUrl = "";
this.currentArtUrlRaw = "";
if (
app.mk.nowPlayingItem != null &&
app.mk.nowPlayingItem.attributes != null &&
app.mk.nowPlayingItem.attributes.artwork != null &&
app.mk.nowPlayingItem.attributes.artwork.url != null &&
app.mk.nowPlayingItem.attributes.artwork.url != ""
) {
if (app.mk.nowPlayingItem != null && app.mk.nowPlayingItem.attributes != null && app.mk.nowPlayingItem.attributes.artwork != null && app.mk.nowPlayingItem.attributes.artwork.url != null && app.mk.nowPlayingItem.attributes.artwork.url != "") {
this.currentArtUrlRaw = this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? "";
this.currentArtUrl = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? "").replace("{w}", artworkSize).replace("{h}", artworkSize);
if (this.mk.nowPlayingItem._assets[0].artworkURL) {
@ -4115,7 +4269,7 @@ const app = new Vue({
if (this.mk.nowPlayingItem._assets[0].artworkURL) {
this.currentArtUrl = this.mk.nowPlayingItem._assets[0].artworkURL;
}
ipcRenderer.send("updateRPCImage", this.currentArtUrl ?? "");
ipcRenderer.send("discordrpc:updateImage", this.currentArtUrl ?? "");
try {
// document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`);
} catch (e) {}
@ -4333,24 +4487,62 @@ const app = new Vue({
// tracks are found in relationship.data
},
setAirPlayCodeUI() {
setAirPlayCodeUI(identifier) {
this.modals.airplayPW = true;
this.currentAirPlayCodeID = identifier;
},
sendAirPlaySuccess() {
sendAirPlaySuccess(silent = false, identifier = "") {
if (!silent) {
notyf.success("Device paired successfully!");
}
console.log("delete idx-pre", identifier);
let idx = this.airplayTrys.findIndex((a) => {
return a.id == identifier;
});
console.log("delete idx", idx);
if (idx != -1) delete this.airplayTrys[idx];
this.airplayTrys = this.airplayTrys.filter((n) => n);
},
sendAirPlayFailed() {
notyf.success("Device paring failed!");
},
airplayDisconnect(dropped, array = []) {
console.log("airplay dropped", dropped, array);
// if (dropped) {
// let [ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv] = array;
// ipcRenderer.send("performAirplayPCM", ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv);
// } else {
// app.activeCasts = [];
// notyf.error("Devices disconnected!");
// }
airplayDisconnect(dropped, array = [], identifier = "") {
console.log("airplay dropped", dropped, array, identifier);
if (dropped) {
let [ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv] = array;
console.log(ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv);
let idx = this.airplayTrys.findIndex((a) => {
return a.id == ipv4 + ":" + ipport + "ap";
});
if (idx == -1) {
this.airplayTrys.push({
id: ipv4 + ":" + ipport + "ap",
attempts: 1,
});
}
idx = this.airplayTrys.findIndex((a) => {
return a.id == ipv4 + ":" + ipport + "ap";
});
if (this.airplayTrys[idx].attempts > 3) {
delete this.airplayTrys[idx];
this.airplayTrys = this.airplayTrys.filter((n) => n);
console.log("delete idx", idx);
return;
} else {
this.airplayTrys[idx].attempts = this.airplayTrys[idx].attempts + 1;
setTimeout(() => {
ipcRenderer.send("performAirplayPCM", ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv, true);
}, 1000);
}
} else {
if (identifier == "") {
app.activeCasts = [];
notyf.error("Devices disconnected!");
} else {
app.activeCasts;
notyf.error("Device disconnected!");
}
}
},
windowFocus(val) {
if (val) {
@ -4446,7 +4638,7 @@ const app = new Vue({
name: app.getLz("action.removeFromLibrary"),
hidden: true,
action: function () {
self.removeFromLibrary();
self.removeFromLibrary(app.mk.nowPlayingItem.type, MusicKitInterop.getAttributes().songId);
},
},
{
@ -4469,8 +4661,13 @@ const app = new Vue({
{
icon: "./assets/feather/user.svg",
name: app.getLz("action.goToArtist"),
action: function () {
action: async function () {
if (app.mk.nowPlayingItem.relationships.artists.data[0].id) {
app.appRoute(`artist/${app.mk.nowPlayingItem.relationships.artists.data[0].id}`);
} else {
const primaryArtist = await MusicKitInterop.fetchSongRelationships({ relationship: "primaryArtist" });
app.appRoute(`artist/${primaryArtist.id}`);
}
},
},
{
@ -4918,7 +5115,36 @@ const app = new Vue({
});
// Load first source
let src = sources[0];
if (src.includes("http")) {
app.mk._services.mediaItemPlayback._currentPlayer._playAssetURL(src, false);
} else {
if (Hls.isSupported()) {
let d = "WIDEVINE_SOFTWARE";
let h = {
initDataTypes: ["cenc", "keyids"],
distinctiveIdentifier: "optional",
persistentState: "required",
};
let p = {
platformInfo: { requiresCDMAttachOnStart: !0, maxSecurityLevel: d, keySystemConfig: h },
appData: { serviceName: "Apple Music" },
};
if (app.radiohls != null && app.radiohls.destroy != null) {
app.radiohls.destroy();
app.radiohls = null;
app.radiohls = new CiderHls();
app.radiohls.loadSource(e);
app.radiohls.attachMedia(app.mk._services.mediaItemPlayback._currentPlayer._targetElement);
app.mk._services.mediaItemPlayback._currentPlayer._targetElement.play();
} else {
app.radiohls = null;
app.radiohls = new CiderHls();
app.radiohls.loadSource(e);
app.radiohls.attachMedia(app.mk._services.mediaItemPlayback._currentPlayer._targetElement);
app.mk._services.mediaItemPlayback._currentPlayer._targetElement.play();
}
}
}
}
}
},

View file

@ -724,7 +724,7 @@ input[type="range"].web-slider::-webkit-slider-runnable-track {
.search-hints-container {
top: 44px;
background: rgb(30 30 30);
// background: rgb(30 30 30);
#cmenu.container();
.search-hints {
@ -1339,6 +1339,12 @@ body[platform="darwin"] .app-chrome .app-chrome-item > .window-controls > div.cl
flex: 1;
}
.live-icon {
filter: none !important;
margin-left: 0.5em !important;
flex: 1;
}
.chrome-icon-container {
display: flex;
position: absolute;

View file

@ -69,6 +69,7 @@
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')"
v-b-tooltip.hover
></div>
<svg class="audio-type live-icon" v-if="mk.nowPlayingItem?.attributes?.isLive === true" :title="$root.getLz('term.live')" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--keyColor)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-b-tooltip.hover><path d="M5 12.55a11 11 0 0 1 14.08 0"></path><path d="M1.42 9a16 16 0 0 1 21.16 0"></path><path d="M8.53 16.11a6 6 0 0 1 6.95 0"></path><line x1="12" y1="20" x2="12.01" y2="20"></line></svg>
</div>
</div>
<template v-if="mk.nowPlayingItem['attributes']['playParams']">
@ -101,10 +102,8 @@
</div>
<div class="app-chrome--center">
<div class="app-chrome-playback-duration-bottom">
<b-row v-if="mkReady()">
<b-col sm="auto" v-if="!mk.nowPlayingItem?.isLiveRadioStation">{{ convertTime(getSongProgress()) }}
</b-col>
<b-col sm="auto" v-else>--:--</b-col>
<b-row v-if="mkReady() && mk.nowPlayingItem?.attributes?.isLive !== true">
<b-col sm="auto">{{ convertTime(getSongProgress()) }}</b-col>
<b-col>
<input type="range" step="0.01" min="0" :style="progressBarStyle()"
@input="playerLCD.desiredDuration = $event.target.value;playerLCD.userInteraction = true"

View file

@ -134,7 +134,7 @@
</b-popover>
<div class="playback-info">
<div class="chrome-icon-container">
<div class="audio-type private-icon" v-if="cfg.general.privateEnabled === true"></div>
<div class="audio-type private-icon" v-if="cfg.general.privateEnabled === true" :title="$root.getLz('term.privateSession')" v-b-tooltip.hover></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
@ -149,6 +149,7 @@
:title="$root.getLz('settings.option.audio.enableAdvancedFunctionality.ciderPPE')"
v-b-tooltip.hover
></div>
<svg class="audio-type live-icon" v-if="mk.nowPlayingItem?.attributes?.isLive === true" :title="$root.getLz('term.live')" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--keyColor)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-b-tooltip.hover><path d="M5 12.55a11 11 0 0 1 14.08 0"></path><path d="M1.42 9a16 16 0 0 1 21.16 0"></path><path d="M8.53 16.11a6 6 0 0 1 6.95 0"></path><line x1="12" y1="20" x2="12.01" y2="20"></line></svg>
</div>
<div class="info-rect">
<div class="song-name"
@ -185,7 +186,7 @@
</div>
</div>
</div>
<div class="song-progress">
<div class="song-progress" v-if="mk.nowPlayingItem?.attributes?.isLive !== true">
<div class="song-duration" style="justify-content: space-between; height: 1px"
:style="[chrome.progresshover ? {'display': 'flex'} : {'display' : 'none'} ]">
<p style="width: auto">{{ convertTime(getSongProgress()) }}</p>
@ -277,10 +278,10 @@
<div class="app-chrome-item search">
<div class="search-input-container">
<div class="search-input--icon"></div>
<input type="search" spellcheck="false" @click="$root.appRoute('search');"
<input type="search" spellcheck="false" @click="$root.appRoute('search');search.showHints = true"
@focus="search.showHints = true"
@blur="setTimeout(()=>{search.showHints = false}, 300)"
v-on:keyup.enter="searchQuery();search.showHints = false;search.cursor = -1" @change="$root.appRoute('search');"
@blur="setTimeout(()=>{if(hintscontext != true){search.showHints = false} }, 300)"
v-on:keyup.enter="searchQuery(search.hints[search.cursor]?.content ?? search.hints[search.cursor]?.searchTerm ?? search.term);search.showHints = false;search.showSearchView = true;search.cursor = -1" @change="$root.appRoute('search');"
v-on:keyup="searchCursor"
@input="getSearchHints()"
:placeholder="$root.getLz('term.search') + '...'" v-model="search.term" ref="searchInput"
@ -288,10 +289,13 @@
<div class="search-hints-container" v-if="search.showHints && search.hints.length != 0">
<div class="search-hints">
<button class="search-hint text-overflow-elipsis" :class="{active: (search.cursor == index)}" v-for="(hint, index) in search.hints"
@click="search.term = hint;search.showHints = false;searchQuery(hint);search.cursor = -1">
{{ hint }}
<button class="search-hint text-overflow-elipsis" :class="{active: (search.cursor == index)}" v-for="(hint, index) in search.hints.filter((a) => {return a.content == null})"
@click="search.term = hint.searchTerm;search.showHints = false;searchQuery(hint.searchTerm);search.cursor = -1">
{{ hint.displayTerm }}
</button>
<template v-for="(item, position) in search.hints.filter((a) => {return a.content != null})">
<mediaitem-smarthints :item="item.content" :position="position"> </mediaitem-smarthints>
</template>
</div>
</div>
</div>

View file

@ -13,7 +13,7 @@
<div class="name">{{app.getLz('action.createPlaylist')}}</div>
</button>
<sidebar-playlist :playlist-select="playlistSelect" :relate-media-items="relateItems"
v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')" :item="item">
v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')" v-bind:key="item.id" :item="item">
</sidebar-playlist>
</div>
<div class="modal-search">

View file

@ -37,7 +37,7 @@
},
enterPassword() {
console.log('Entered passCode: ', this.passcode)
ipcRenderer.send("setAirPlayPasscode", this.passcode)
ipcRenderer.send("setAirPlayPasscode", this.passcode, this.$root.currentAirPlayCodeID)
this.close()
}
}

View file

@ -54,9 +54,13 @@
<small>{{ device.host }}</small>
</div>
<div class="md-option-segment_auto"
style="display: flex;justify-content: center;align-items: center"
style="display: flex;justify-content: center;align-items: center" @click="disconnectAirPlayCast(device)"
v-if="activeCasts.some(item => { return item.host == device.host && item.name == device.name && item.port == device.port})">
Connected
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20px" height="20px" viewBox="0 0 20 20" version="1.1">
<g id="surface1">
<path style="stroke:none;fill-rule:evenodd;fill:#fff;fill-opacity:1;" d="M 10 0 C 15.523438 0 20 4.476562 20 10 C 20 15.523438 15.523438 20 10 20 C 4.476562 20 0 15.523438 0 10 C 0 4.476562 4.476562 0 10 0 Z M 12.136719 5.988281 C 12.421875 5.703125 12.597656 5.472656 12.953125 5.828125 L 14.089844 6.988281 C 14.464844 7.355469 14.445312 7.570312 14.089844 7.914062 L 11.933594 10.0625 L 14.011719 12.136719 C 14.296875 12.421875 14.527344 12.597656 14.171875 12.953125 L 13.011719 14.089844 C 12.644531 14.464844 12.429688 14.445312 12.089844 14.089844 L 10 12 L 7.914062 14.082031 C 7.574219 14.433594 7.359375 14.453125 6.992188 14.082031 L 5.828125 12.945312 C 5.472656 12.59375 5.703125 12.417969 5.992188 12.128906 L 8.066406 10.0625 L 5.917969 7.917969 C 5.566406 7.574219 5.546875 7.359375 5.917969 6.992188 L 7.054688 5.828125 C 7.40625 5.472656 7.582031 5.703125 7.871094 5.992188 L 10 8.128906 Z M 12.136719 5.988281 "/>
</g>
</svg>
</div>
<div class="md-option-segment_auto" v-else
style="display: flex;justify-content: center;align-items: center">
@ -135,8 +139,27 @@
ipcRenderer.send('performGCCast', device, "Cider", "Playing ...", "Test build", '');
},
setAirPlayCast(device) {
if (!this.activeCasts.some(item => { return item.host == device.host && item.name == device.name && item.port == device.port})) {
this.activeCasts.push(device);
ipcRenderer.send("performAirplayPCM", device.host, device.port, null, "", "", "", "", device.txt, device.airplay2)
ipcRenderer.send("performAirplayPCM", device.host, device.port, null, "", "", "", "", device.txt, device.airplay2, false)}
},
disconnectAirPlayCast(device) {
app.confirm("Do you want to disconnect this device?",(res) => {
if (res){
ipcRenderer.send('disconnectAirplay', device.host +":"+ device.port+"ap");
console.log('disconnectAirplay', device.host +":"+ device.port+"ap")
let idx = this.activeCasts.findIndex(((a) => {return a.host == device.host && a.port == device.port}))
console.log(idx)
if (idx != -1) {
delete this.activeCasts[idx]
delete this.$root.activeCasts[idx]
this.activeCasts = this.activeCasts.filter(a => {return !(a.host == device.host && a.port == device.port)})
console.log(this.activeCasts)
if (this.activeCasts.length == 0){
this.stopCasting()
}
}}
})
},
stopCasting() {
CiderAudio.stopAudio();

View file

@ -39,7 +39,7 @@
</div>
<div class="row fs-row" v-if="tabMode != 'catalog'">
<div class="col artwork-col">
<div class="artwork" @click="app.fullscreen(false)">
<div class="artwork" :class="$root.mk.isPlaying && 'playing'" @click="app.fullscreen(false)">
<mediaitem-artwork
:size="600"
:video="video"

View file

@ -4,6 +4,7 @@
<div v-for="items in itemPages">
<mediaitem-list-item
v-for="(song, index) in items" :show-library-status="showLibraryStatus"
v-bind:key="song.id"
:parent="'listitem-hr' + simplifiedParent"
:index="song.index"
:item="song"></mediaitem-list-item>

View file

@ -494,6 +494,10 @@
"name": `${app.getLz('action.share')} (song.link)`,
"action": async function() {
let item = self.item
if(item.type.startsWith('library-')) {
self.item.attributes.url = self.item.relationships.catalog.data[0].attributes.url
item.attributes.url = item.relationships.catalog.data[0].attributes.url
}
if (!item.attributes.url) {
if (item.type.includes("library")) {
let result = (await app.mk.api.v3.music(`/v1/me/library/${item.type.replace("library-", '')}/${item.id}/catalog`)).data.data[0]

View file

@ -1,5 +1,5 @@
<script type="text/x-template" id="mediaitem-mvview-sp">
<div style="position: relative; display: inline-flex;">
<div v-if="(item.attributes?.editorialArtwork?.subscriptionHero?.url ?? item.attributes?.artwork?.url ?? '') !='' " style="position: relative; display: inline-flex;">
<div @click.self='log(item);app.routeView(item)'
class="cd-mediaitem-mvview">
<div style="height: 70px; min-height: 70px; max-height: 70px; width: 100%; margin-left: 5px;">
@ -15,7 +15,7 @@
</div>
<div class="artwork">
<mediaitem-artwork
:url="item.attributes?.editorialArtwork.subscriptionHero.url ?? item.attributes?.artwork"
:url="item.attributes?.editorialArtwork?.subscriptionHero?.url ?? item.attributes?.artwork?.url"
: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="516" :width="900"
></mediaitem-artwork>

View file

@ -6,13 +6,14 @@
((item?.attributes?.kind != null || item?.attributes?.type == 'editorial-elements')
? item :
((item.relationships && item.relationships.contents ) ? item.relationships.contents.data[0] : item)) ?? (item)"
:imagesize="imagesize"
:imagesize="imagesize" v-bind:key="item.id"
:badge="item.attributes ?? [] " v-for="item in items"></mediaitem-mvview-sp>
</template>
<template v-else>
<mediaitem-square :kind="kind" size="600" :key="item?.id ?? ''"
:item="item ? ((item.attributes?.kind != null || item.type == 'editorial-elements') ? item : ((item.relationships && item.relationships.contents ) ? item.relationships.contents.data[0] : item)) : []"
:imagesize="imagesize"
v-bind:key="item.id"
v-for="item in items"></mediaitem-square>
</template>
</vue-horizontal>

View file

@ -2,7 +2,7 @@
<div class="cd-hmedia-scroller hmedia-scroller-card">
<vue-horizontal>
<template>
<mediaitem-square kind="card" :item="item" size="300" :reasonShown="withReason"
<mediaitem-square kind="card" :item="item" size="300" :reasonShown="withReason" v-bind:key="item.id"
v-for="item in items"></mediaitem-square>
</template>
</vue-horizontal>

View file

@ -32,7 +32,7 @@
<%- include("../svg/play.svg") %>
</button>
<div class="badge-container" v-if="itemBadges.length != 0">
<div class="socialBadge" v-for="badge in itemBadges.limit(1)">
<div class="socialBadge" v-for="badge in itemBadges.limit(1)" v-bind:key="badge.id">
<mediaitem-artwork
:url="(badge.attributes.artwork ? badge.attributes.artwork.url : '')"
:size="32"></mediaitem-artwork>
@ -45,9 +45,9 @@
<div class="title"
:title="item.attributes?.name ?? (item.relationships?.contents?.data[0]?.attributes?.name ?? (item.attributes?.editorialNotes?.name ?? ''))"
v-if="item.attributes.artistNames == null || kind != 'card'" @click='app.routeView(item)'>
<div class="item-navigate text-overflow-elipsis">{{ item.attributes?.name.replace(/&nbsp;/g, ' ').replace(/Apple Music |^Apple |/g, '') ??
<div class="item-navigate text-overflow-elipsis">{{ (item.attributes?.editorialElementKind == "394" && item.relationships?.contents?.data[0]?.attributes?.shortName != null ) ? item.relationships?.contents?.data[0]?.attributes?.shortName : (item.attributes?.name ? ((removeamtext ) ? item.attributes?.name.replace(/&nbsp;/g, ' ').replace(/Apple Music |^Apple |/g, '') : item.attributes?.name.replace(/&nbsp;/g, ' ')) :
(item.relationships?.contents?.data[0]?.attributes?.name ??
(item.attributes?.editorialNotes?.name ?? '')) }}
(item.attributes?.editorialNotes?.name ?? ''))) }}
</div>
<div class="explicit-icon" v-if="item.attributes && item.attributes.contentRating == 'explicit'"
style="background-image: url(./assets/explicit.svg);height: 12px;width: 12px;filter: contrast(0);background-repeat: no-repeat;margin-top: 2.63px;margin-left: 4px;"></div>
@ -98,6 +98,11 @@ Vue.component('mediaitem-square', {
default: 'cc',
required: false
},
removeamtext: {
type: Boolean,
default: false,
required: false
},
'contextExt': { type: Object, required: false },
},
data: function () {
@ -571,7 +576,7 @@ Vue.component('mediaitem-square', {
icon: "./assets/feather/play.svg",
name: app.getLz('action.startRadio'),
action: () => {
app.mk.setStationQueue({ artist: this.item.id }).then(() => {
app.mk.setStationQueue({ artist: 'a-'+this.item.id }).then(() => {
app.mk.play()
})
}
@ -581,7 +586,7 @@ Vue.component('mediaitem-square', {
icon: "./assets/feather/share.svg",
name: app.getLz('term.share'),
action: () => {
self.app.copyToClipboard(this.item.id.attributes.url)
self.app.copyToClipboard(this.item.attributes.url)
}
},
{

View file

@ -1,5 +1,5 @@
<script type="text/x-template" id="cider-menu-panel">
<div class="menu-panel" @click.self="menuPanel.visible = false" @contextmenu.self="menuPanel.visible = false">
<div class="menu-panel" @click.self="menuPanel.visible = false; if($root.hintscontext){$root.hintscontext = false;focusOther()}" @contextmenu.self="menuPanel.visible = false; if($root.hintscontext){$root.hintscontext = false;focusOther()}">
<div class="menu-panel-body" ref="menubody" :style="elStyle" :class="getBodyClasses()">
<div class="menu-header-text" v-if="content.name != ''">
@ -56,12 +56,22 @@
mounted() {
if (this.event) {
this.position = [this.event.clientX, this.event.clientY];
}
this.$nextTick(() => {
// this.size = [document.querySelector(".menu-panel-body").offsetWidth, document.querySelector(".menu-panel-body").offsetHeight];
// ugly hack
setTimeout(this.getStyle, 1)
setTimeout(this.getStyle, 0.8)
});
}
else {
document.addEventListener('mouseover', event => {
this.event = event
this.position = [event.clientX, event.clientY]
console.log("pos", this.position)
this.$nextTick(() => {
setTimeout(this.getStyle, 0.8)})
},{once: true})
}
},
methods: {
getBodyClasses() {
@ -78,6 +88,9 @@
return "active";
}
},
focusOther(){
document.querySelector('.search-input-container input').focus()
},
getStyle() {
let style = {}
this.size = [this.$refs.menubody.offsetWidth, this.$refs.menubody.offsetHeight];
@ -132,7 +145,7 @@
action(item) {
item.action()
if (!item["keepOpen"]) {
this.menuPanel.visible = false
this.menuPanel.visible = false;if(app.hintscontext){ app.hintscontext = false; document.querySelector('.search-input-container input').focus(); app.search.showHints = false}
}
}
}

View file

@ -15,6 +15,7 @@
</div>
<div class="queue-body" v-if="page == 'history'">
<mediaitem-list-item :show-library-status="false" v-for="item in history"
v-bind:key="item.id"
:item="item"></mediaitem-list-item>
</div>
<div class="queue-body" v-if="page == 'queue'">

View file

@ -471,6 +471,22 @@
</label>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{'Display Style'}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<select class="md-select"
v-model="$root.cfg.visual.overrideDisplayTheme"
@change="changeDisplayTheme">
<option value="system">System</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
</select>
</label>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.visual.windowBackgroundStyle')}}
@ -905,7 +921,20 @@
</div>
</div>
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.reload')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" @click="reloadDiscordRPC()">
{{$root.getLz('menubar.options.reload')}}
</button>
</div>
</div>
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.clientName')}}
</div>
@ -920,7 +949,7 @@
</div>
</div>
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.clearOnPause')}}
</div>
@ -932,31 +961,19 @@
</div>
</div>
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.hideButtons')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox"
v-model="app.cfg.connectivity.discord_rpc.hide_buttons" switch />
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.hideTimestamp')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox"
v-model="app.cfg.connectivity.discord_rpc.hide_timestamp" switch />
v-model="app.cfg.connectivity.discord_rpc.activity.hide_timestamp" switch />
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.detailsFormat')}}<br />
<small>{{$root.getLz('term.variables')}}: {artist}, {composer}, {title},
@ -966,12 +983,12 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="text"
v-model="app.cfg.connectivity.discord_rpc.details_format" />
v-model="app.cfg.connectivity.discord_rpc.activity.details_format" />
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.stateFormat')}}
<small>{{$root.getLz('term.variables')}}: {artist}, {composer}, {title},
@ -981,19 +998,51 @@
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="text"
v-model="app.cfg.connectivity.discord_rpc.state_format" />
v-model="app.cfg.connectivity.discord_rpc.activity.state_format" />
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.enabled != false">
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.reload')}}
{{$root.getLz('settings.option.connectivity.discordRPC.showActivityButtons')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<button class="md-btn" @click="reloadDiscordRPC()">
{{$root.getLz('menubar.options.reload')}}
</button>
<label>
<input type="checkbox"
v-model="app.cfg.connectivity.discord_rpc.activity.buttons.enabled" switch />
</label>
</div>
</div>
<div v-show="app.cfg.connectivity.discord_rpc.activity.buttons.enabled == true">
<div class="md-option-line">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.firstButton')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<select class="md-select" v-model="app.cfg.connectivity.discord_rpc.activity.buttons.first" @change="if ($event.target.value == 'disabled') app.cfg.connectivity.discord_rpc.activity.buttons.second = 'disabled';">
<option v-for="option in app.cfg.connectivity.discord_rpc.activity.buttons.options" v-bind:value="option" v-show="app.cfg.connectivity.discord_rpc.activity.buttons.second != option">{{ $root.getLz(`settings.option.connectivity.discordRPC.buttons.${option}`) }}</option>
<option value="disabled">{{$root.getLz('term.disabled')}}</option>
</select>
</label>
</div>
</div>
<div class="md-option-line" v-show="app.cfg.connectivity.discord_rpc.activity.buttons.first != 'disabled'">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.discordRPC.secondButton')}}
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<select class="md-select" v-model="app.cfg.connectivity.discord_rpc.activity.buttons.second">
<option v-for="option in app.cfg.connectivity.discord_rpc.activity.buttons.options" v-bind:value="option" v-show="app.cfg.connectivity.discord_rpc.activity.buttons.first != option">{{ $root.getLz(`settings.option.connectivity.discordRPC.buttons.${option}`) }}</option>
<option value="disabled">{{$root.getLz('term.disabled')}}</option>
</select>
</label>
</div>
</div>
</div>
</div>
@ -1069,11 +1118,12 @@
<div class="md-option-line" v-show="app.cfg.connectivity.lastfm.enabled">
<div class="md-option-segment">
{{$root.getLz('settings.option.connectivity.lastfmScrobble.filterTypes')}}
<small>{{$root.getLz('settings.option.connectivity.lastfmScrobble.filterTypes.description')}}</small>
</div>
<div class="md-option-segment md-option-segment_auto">
<label>
<input type="checkbox" @change="filterChange" value="song">{{$root.getLz('term.songs')}}<br>
<input type="checkbox" @change="filterChange" value="musicVideo">{{$root.getLz('term.musicVideos')}}<br>
<input type="checkbox" v-model="app.cfg.connectivity.lastfm.filter_types['song']">{{$root.getLz('term.songs')}}<br>
<input type="checkbox" v-model="app.cfg.connectivity.lastfm.filter_types['musicVideo']">{{$root.getLz('term.musicVideos')}}
</label>
</div>
</div>
@ -1442,6 +1492,9 @@
openAppData() {
ipcRenderer.send('open-appdata')
},
changeDisplayTheme(){
ipcRenderer.send('changeDisplayTheme',app.cfg.visual.overrideDisplayTheme)
},
getLanguages: function() {
let langs = this.$root.lzListing
let categories = {
@ -1508,7 +1561,7 @@
ipcRenderer.send('cc-logout')
},
reloadDiscordRPC() {
ipcRenderer.send('reloadRPC')
ipcRenderer.send('discordrpc:reload')
},
lfmDisconnect() {
this.$root.cfg.connectivity.lastfm.enabled = false;

View file

@ -15,7 +15,7 @@
<div class="name">{{app.getLz('action.createPlaylist')}}</div>
</button>
<sidebar-playlist :playlist-select="playlistSelect"
v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')" :item="item">
v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')" v-bind:key="item.id" :item="item">
</sidebar-playlist>
</div>
<div class="modal-search">

View file

@ -19,7 +19,7 @@
<div class="folder-body" v-if="item.type === 'library-playlist-folders' && folderOpened">
<template v-if="children.length != 0">
<sidebar-playlist v-for="item in children" :relate-media-items="relateMediaItems"
:playlist-select="playlistSelect" :item="item" :key="item.id"></sidebar-playlist>
:playlist-select="playlistSelect" :item="item" v-bind:key="item.id"></sidebar-playlist>
</template>
<template v-else>
<div class="spinner"></div>

View file

@ -7,10 +7,10 @@
<input
type="search"
spellcheck="false"
@click="$root.appRoute('search');"
@click="$root.appRoute('search');$root.search.showHints = true"
@focus="$root.search.showHints = true"
@blur="$root.setTimeout(()=>{$root.search.showHints = false}, 300)"
v-on:keyup.enter="$root.searchQuery();$root.search.showHints = false;$root.search.cursor = -1"
@blur="$root.setTimeout(()=>{if($root.hintscontext != true){$root.search.showHints = false} }, 300)"
v-on:keyup.enter="$root.searchQuery($root.search.hints[$root.search.cursor]?.content ?? $root.search.hints[$root.search.cursor]?.searchTerm ?? $root.search.term);$root.search.showHints = false;$root.search.showSearchView = true;$root.search.cursor = -1"
v-on:keyup="$root.searchCursor"
@change="$root.appRoute('search');"
@input="$root.getSearchHints()"
@ -27,11 +27,14 @@
<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);$root.search.cursor = -1"
v-for="(hint, index) in $root.search.hints.filter((a) => {return a.content == null})" :class="{active: ($root.search.cursor == index)}"
@click="$root.search.term = hint.searchTerm;$root.search.showHints = false;$root.searchQuery(hint.searchTerm);$root.search.cursor = -1"
>
{{ hint }}
{{ hint.displayTerm }}
</button>
<template v-for="(item, position) in $root.search.hints.filter((a) => {return a.content != null})">
<mediaitem-smarthints :item="item.content" :position="position"> </mediaitem-smarthints>
</template>
</div>
</div>
</div>
@ -162,6 +165,7 @@
<template v-if="!$root.cfg.general.sidebarCollapsed.amplaylists">
<sidebar-playlist
v-for="item in $root.getPlaylistFolderChildren('p.applemusic')"
v-bind:key="item.id"
:item="item"
>
</sidebar-playlist>
@ -184,6 +188,7 @@
</button>
<sidebar-playlist
v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')"
v-bind:key="item.id"madeforyou
:item="item"
>
</sidebar-playlist>

View file

@ -0,0 +1,490 @@
<script type="text/x-template" id="mediaitem-smarthints">
<div class="cd-queue-item" @click="$root.search.showHints = false;$root.routeView(item);$root.search.cursor = -1;$root.search.term == ''" @contextmenu="$root.hintscontext = true;getContextMenu()"
:class="{'hintactive': ($root.search.cursor == position + $root.search.hints.filter((a) => {return a.content == null}).length)}">
<div class="row" @contextmenu="$root.hintscontext = true;getContextMenu()">
<div class="col-auto cider-flex-center" @contextmenu="$root.hintscontext = true;getContextMenu()">
<div class="artwork" :class="{'circle': item.type == 'artists'}">
<mediaitem-artwork
:url="item.attributes.artwork ? item.attributes.artwork.url : ''"
:size="32"
:style="{'position': 'relative', 'z-index': '-1'}"
></mediaitem-artwork>
<button class="circular-play-button" @click.stop="playTrack(item)">
<div class="_svg-icon" style="--icon:url(\.\/assets\/play\.svg); width: 15px;">
</div>
</button>
</div>
</div>
<div class="col queue-info" @contextmenu="$root.hintscontext = true;getContextMenu()">
<div class="queue-title text-overflow-elipsis">{{ item.attributes.name }}
</div>
<div class="queue-subtitle text-overflow-elipsis">{{
item.attributes.artistName }}
</div>
</div>
<div class="queue-explicit-icon cider-flex-center"
v-if="item.attributes.contentRating == 'explicit'">
<div class="explicit-icon"></div>
</div>
<!-- <div class="col queue-duration-info">
<div class="queue-duration cider-flex-center">
{{convertTimeToString(item.content.attributes.durationInMillis)}}
</div>
</div> -->
</div>
</div>
</script>
<script>
Vue.component('mediaitem-smarthints', {
template: '#mediaitem-smarthints',
props: {
item: {
type: Object,
required: true
},
position: {
type: Number,
required: true
}
},
data: function () {
return {
app: this.$root,
guid: this.uuidv4(),
}
},
async mounted() {
},
methods: {
async isInLibrary() {
if (this.item.type && !this.item.type.includes("library")) {
let params = {
relate: "library",
"fields": "inLibrary",
"extend": this.revisedRandId()
}
let kind = this.item.type ?? this.item.attributes.playParams.kind
let truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
if (truekind === "musicVideos") {
truekind = "music-videos"
}
let res = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/?ids[${truekind}]=${this.item.attributes.playParams.id ?? this.item.id}`, params);
res = res.data.data[0]
this.addedToLibrary = (res && res.attributes && res.attributes.inLibrary) ? res.attributes.inLibrary : false
} else {
this.addedToLibrary = true
}
},
revisedRandId() {
return Math.random().toString(36).replace(/[^a-z]+/g, '').slice(2, 10);
},
uuidv4() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
},
getContextMenu(event) {
if (event){
if (this.item.type === "artists") {
return this.artistMenu(event)
} else {
return this.contextMenu(event)
}
} else {
document.addEventListener('mouseover', event => {
if (this.item.type === "artists") {
return this.artistMenu(event)
} else {
return this.contextMenu(event)
}
},{once: true})}
},
async contextMenu(event) {
let self = this
let useMenu = "normal"
if (app.selectedMediaItems.length <= 1) {
app.selectedMediaItems = []
app.select_selectMediaItem(this.item.attributes.playParams.id ?? this.item.id, this.item.attributes.playParams.kind ?? this.item.type, this.index, this.guid, this.item.attributes.playParams.isLibrary ?? false)
} else {
useMenu = "multiple"
}
let menus = {
multiple: {
items: [
{
name: app.getLz('action.playTracksNext').replace("${app.selectedMediaItems.length}", app.selectedMediaItems.length),
"icon": "./assets/arrow-bend-up.svg",
action: () => {
let itemsToPlay = {}
app.selectedMediaItems.forEach(item => {
if (!itemsToPlay[item.kind]) {
itemsToPlay[item.kind] = []
}
itemsToPlay[item.kind].push(item.id)
})
// loop through itemsToPlay
for (let kind in itemsToPlay) {
let ids = itemsToPlay[kind]
if (ids.length > 0) {
app.mk.playNext({ [kind + "s"]: itemsToPlay[kind] })
}
}
console.log(itemsToPlay)
app.selectedMediaItems = []
}
},
{
name: app.getLz('action.playTracksLater').replace("${app.selectedMediaItems.length}", app.selectedMediaItems.length),
"icon": "./assets/arrow-bend-down.svg",
action: () => {
let itemsToPlay = {}
app.selectedMediaItems.forEach(item => {
if (!itemsToPlay[item.kind]) {
itemsToPlay[item.kind] = []
}
itemsToPlay[item.kind].push(item.id)
})
// loop through itemsToPlay
for (let kind in itemsToPlay) {
let ids = itemsToPlay[kind]
if (ids.length > 0) {
app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] })
}
}
app.selectedMediaItems = []
}
},
]
},
normal: {
headerItems: [
{
"icon": "./assets/feather/heart.svg",
"id": "love",
"name": app.getLz('action.love'),
"hidden": false,
"disabled": true,
"action": function () {
app.love(self.item)
}
},
{
"icon": "./assets/feather/heart.svg",
"id": "unlove",
"active": true,
"name": app.getLz('action.unlove'),
"hidden": true,
"action": function () {
app.unlove(self.item)
}
},
{
"icon": "./assets/feather/thumbs-down.svg",
"id": "dislike",
"name": app.getLz('action.dislike'),
"hidden": false,
"disabled": true,
"action": function () {
app.dislike(self.item)
}
},
{
"icon": "./assets/feather/thumbs-down.svg",
"id": "undo_dislike",
"name": app.getLz('action.undoDislike'),
"active": true,
"hidden": true,
"action": function () {
app.unlove(self.item)
}
},
],
items: [
{
"icon": "./assets/feather/list.svg",
"id": "addToPlaylist",
"name": app.getLz('action.addToPlaylist'),
"action": function () {
app.promptAddToPlaylist()
}
},
{
"id": "addToLibrary",
"icon": "./assets/feather/plus.svg",
"name": app.getLz('action.addToLibrary'),
"hidden": false,
"disabled": true,
"action": function () {
let item_id = self.item.attributes.playParams.id ?? self.item.id;
let data_type = self.item.attributes.playParams.kind ?? self.item.type;
app.addToLibrary(item_id);
self.addedToLibrary = true;
}
},
{
"id": "removeFromLibrary",
"icon": "./assets/feather/x-circle.svg",
"name": app.getLz('action.removeFromLibrary'),
"hidden": true,
"action": async function () {
console.log("remove");
let item_id = self.item.attributes.playParams.id ?? self.item.id;
let data_type = self.item.attributes.playParams.kind ?? self.item.type;
await self.removeFromLibrary(item_id);
self.addedToLibrary = false
}
},
{
"name": app.getLz('action.playNext'),
"icon": "./assets/arrow-bend-up.svg",
"action": function () {
app.mk.playNext({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
app.mk.queue._reindex()
app.selectedMediaItems = []
}
},
{
"name": app.getLz('action.playLater'),
"icon": "./assets/arrow-bend-down.svg",
"action": function () {
app.mk.playLater({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
app.mk.queue._reindex()
app.selectedMediaItems = []
}
},
{
"icon": "./assets/feather/share.svg",
"name": app.getLz('action.share'),
"action": function () {
if (!self.item.attributes.url && self.item.relationships) {
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)
})
}
} else {
self.app.copyToClipboard(self.item.attributes.url)
}
}
},
{
"icon": "./assets/feather/share.svg",
"name": `${app.getLz('action.share')} (song.link)`,
"action": function () {
if (!self.item.attributes.url && self.item.relationships) {
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)
})
}
} else {
self.app.songLinkShare(self.item.attributes.url)
}
}
}
]
}
}
if ((self.item.attributes.playParams.kind ?? self.item.type).includes("playlist")) {
// remove the add to playlist option by id "addToPlaylist" using the .filter() method
menus.normal.items = menus.normal.items.filter(function (item) {
return item.id != "addToPlaylist"
})
}
app.showMenuPanel(menus[useMenu], event)
try {
await this.isInLibrary().then((_) => {
if (self.addedToLibrary) {
menus.normal.items.find(x => x.id == 'addToLibrary').hidden = true
menus.normal.items.find(x => x.id == 'removeFromLibrary').hidden = false
} else {
menus.normal.items.find(x => x.id == 'addToLibrary').disabled = false
}
})
} catch (e) {
console.log(e)
}
try {
let rating = await app.getRating(self.item)
if (rating == 0) {
menus.normal.headerItems.find(x => x.id == 'love').disabled = false
menus.normal.headerItems.find(x => x.id == 'dislike').disabled = false
} else if (rating == 1) {
menus.normal.headerItems.find(x => x.id == 'unlove').hidden = false
menus.normal.headerItems.find(x => x.id == 'love').hidden = true
} else if (rating == -1) {
menus.normal.headerItems.find(x => x.id == 'undo_dislike').hidden = false
menus.normal.headerItems.find(x => x.id == 'dislike').hidden = true
}
} catch (err) {
}
if (this.contextExt) {
if (this.contextExt.normal) {
menus.normal.items = menus.normal.items.concat(this.contextExt.normal)
}
if (this.contextExt.multiple) {
menus.multiple.items = menus.multiple.items.concat(this.contextExt.multiple)
}
}
},
async artistMenu(event) {
let self = this
let followAction = "follow"
let followActions = {
follow: {
icon: "./assets/star.svg",
name: app.getLz('action.favorite'),
action: () => {
self.$root.setArtistFavorite(this.item.id, true)
}
},
unfollow: {
icon: "./assets/star.svg",
name: app.getLz('action.removeFavorite'),
action: () => {
self.$root.setArtistFavorite(this.item.id, false)
}
}
}
if (self.app.cfg.home.followedArtists.includes(this.item.id)) {
followAction = "unfollow"
}
app.showMenuPanel({
items: [
{
icon: "./assets/feather/play.svg",
name: app.getLz('action.startRadio'),
action: () => {
app.mk.setStationQueue({ artist: 'a-'+this.item.id }).then(() => {
app.mk.play()
})
}
},
followActions[followAction],
{
icon: "./assets/feather/share.svg",
name: app.getLz('term.share'),
action: () => {
self.app.copyToClipboard(this.item.attributes.url)
}
},
{
icon: "./assets/feather/external-link.svg",
name: app.getLz('action.openArtworkInBrowser'),
action: () => {
window.open(app.getMediaItemArtwork(this.getArtworkUrl(), 1024, 1024))
}
},
]
}, event)
},
getArtworkUrl(size = -1, includeUrl = false) {
let artwork = this.item.attributes.artwork ? this.item.attributes.artwork.url : ''
if (size != -1) {
artwork = artwork.replace('{w}', size).replace('{h}', size).replace('{f}', "webp").replace('{c}', ((size === 900) ? "sr" : "cc"))
}
switch (this.kind) {
case "385":
artwork = this.item.attributes?.editorialArtwork?.subscriptionHero?.url
break;
}
if (!includeUrl) {
return artwork
} else {
return `url("${artwork}")`
}
},
playTrack(item) {
let parent = this.parent
let childIndex = this.index
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 isLibrary = item.attributes.playParams ? (item.attributes.playParams?.isLibrary ?? false) : false;
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"))
app.mk.stop().then(() => {
if (parent != null && childIndex != null) {
app.queueParentandplayChild(parent, childIndex, item);
} else if (kind.includes("playlist") && (id.startsWith("p.") || id.startsWith("pl."))) {
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
app.mk.setQueue({
[truekind]: [item.attributes.playParams?.id ?? item.id],
parameters: { l: this.app.mklang }
}).then(function() {
app.mk.play().then(function() {
var playlistId = id
function getPlaylist(id, isLibrary) {
if (isLibrary) {
return this.app.mk.api.v3.music(`/v1/me/library/playlists/${id}`)
} else {
return this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/playlists/${id}`)
}
}
try {
getPlaylist(id, isLibrary).then(res => {
//let query = res.relationships.tracks.data.map(item => new MusicKit.MediaItem(item));
//if (app.mk.shuffleMode == 1){shuffleArray(query); }
// console.log(query)
// app.mk.queue.append(query)
if (!res.data.relationships.tracks.next) {
return
} else {
getPlaylistTracks(res.data.relationships.tracks.next)
}
function getPlaylistTracks(next) {
app.apiCall(app.musicBaseUrl + next, res => {
// if (res.id != playlistId || next.includes(playlistId)) {
// return
// }
console.log('nextres', res)
let query = res.data.map(item => new MusicKit.MediaItem(item))
if (app.mk.shuffleMode == 1) {
shuffleArray(query);
console.log('shf')
}
app.mk.queue.append(query)
if (res.next) {
getPlaylistTracks(res.next)
}
})
}
})
} catch (e) {
}
})
})
} else {
app.playMediaItemById(item.attributes.playParams?.id ?? item.id, item.attributes.playParams?.kind ?? item.type, item.attributes.playParams?.isLibrary ?? false, item.attributes.url)
}
})
},
},
beforeDestroy: function () {
// this.item = null;
// this.kind = null;
// this.size = null;
}
});
</script>

View file

@ -63,7 +63,6 @@
<body class="notransparency" oncontextmenu="return false;" loading="1" os-release="<%= parseInt(env.osRelease) ;%>"
platform="<%= env.platform ;%>">
<script src="<%- (env.dev ? " ./lib/vue.js" : "./lib/vue.dev.js"); %>"></script>
<script src="./lib/vue-horizontal.js"></script>
<script src="./lib/bootstrap-vue.min.js"></script>
<script src="./lib/vuex.min.js"></script>

View file

@ -1,7 +1,7 @@
<script type="text/x-template" id="apple-account-settings">
<div style="display:flex;width:100%;height:100%;padding-top: var(--navigationBarHeight);position:absolute;top:0;left:0;">
<webview id="foo"
src="https://music.apple.com/includes/commerce/account/settings?product=music&isFullscreen=true&isModal=false"
src="https://beta.music.apple.com/includes/commerce/account/settings?product=music&isFullscreen=false&isModal=false&expectsModalLayout=false&isFullScreen=true"
style="display:inline-flex; width:100%;"></webview>
</div>
</script>

View file

@ -1,6 +1,6 @@
<script type="text/x-template" id="cider-applecurator">
<div class="content-inner">
<h1 class="header-text">{{ data.attributes.shortName ?? data.attributes.name}}</h1>
<h1 class="header-text">{{ data.attributes?.shortName ?? data.attributes.name}}</h1>
<template v-if="data.relationships && data.relationships.grouping">
<template
v-for="(recom,index) in data.relationships.grouping.data[0].relationships.tabs.data[0].relationships.children.data">
@ -43,6 +43,9 @@
return {
app: this.$root
}
}
},
mounted() {
console.log('ping')
},
})
</script>

View file

@ -41,7 +41,7 @@
</div>
<div class="well" style="margin-top:0;">
<template v-if="artistFeed.length > 0">
<mediaitem-list-item v-for="item in artistFeed" :item="item"></mediaitem-list-item>
<mediaitem-list-item v-for="item in artistFeed" v-bind:key="item.id" :item="item"></mediaitem-list-item>
</template>
<template v-else>
<div class="spinner"></div>

View file

@ -8,7 +8,22 @@
<h1 class="header-text">{{data.attributes?.title ?? ""}}</h1>
<h2 class="header-desc" v-html='data.relationships?.children?.data[0]?.attributes?.description ?? ""'></h2>
<template v-if="data.relationships">
<template v-for="(recom,index) in data.relationships.children.data">
<template v-if="data.type=='rooms' && (data?.relationships?.contents?.data ?? []).length > 0">
<!-- <div class="row">
<div class="col-auto cider-flex-center"
v-if="data?.relationships?.contents?.data.length > 10">
<button class="cd-btn-seeall"
@click="app.showCollection(recom, data.attributes.name ?? '', 'listen_now')">
{{app.getLz('term.seeAll')}}
</button>
</div>
</div> -->
<template>
<mediaitem-square :item="item" :key="item?.id ?? ''"
v-for="item in data?.relationships?.contents?.data"></mediaitem-square>
</template>
</template>
<template v-else v-for="(recom,index) in (data.relationships?.children?.data ?? recom?.relationships?.contents?.data)">
<template v-if="(recom.relationships?.contents?.data ?? []).length > 0">
<div class="row">
<div class="col" v-if="recom.attributes.name != 'Chart Set'">

View file

@ -243,6 +243,7 @@
:showIndex="true"
:showIndexPlaylist="(data.attributes.playParams?.kind ?? data.type ?? '').includes('playlist')"
:context-ext="buildContextMenu()"
v-bind:key="item.id"
v-for="(item,index) in currentSlice"></mediaitem-list-item>
</template>
<template v-else>
@ -255,6 +256,7 @@
:showIndex="true"
:showIndexPlaylist="(data.attributes.playParams?.kind ?? data.type ?? '').includes('playlist')"
:context-ext="buildContextMenu()"
v-bind:key="item.id"
v-for="(item,index) in disc.tracks"></mediaitem-list-item>
</div>
@ -645,6 +647,7 @@
return {
normal: [
{
icon: "./assets/feather/x-circle.svg",
name: app.getLz('action.removeFromPlaylist'),
action: () => {
self.remove()
@ -653,6 +656,7 @@
],
multiple: [
{
icon: "./assets/feather/x-circle.svg",
name: app.getLz('action.removeFromPlaylist'),
action: () => {
self.remove()

View file

@ -17,7 +17,7 @@
<div class="well artistfeed-well">
<template v-if="isSectionReady('recentlyPlayed')">
<mediaitem-list-item v-for="item in recentlyPlayed.limit(6)"
:item="item"></mediaitem-list-item>
:item="item" v-bind:key="item.id"></mediaitem-list-item>
</template>
<div class="spinner" v-else></div>
</div>
@ -39,7 +39,7 @@
</div>
<div class="well artistfeed-well" style="margin-top:0;">
<template v-if="artistFeed.length > 0">
<mediaitem-list-item v-for="item in artistFeed.limit(6)" :item="item"></mediaitem-list-item>
<mediaitem-list-item v-for="item in artistFeed.limit(6)" :item="item" v-bind:key="item.id"></mediaitem-list-item>
</template>
<div class="spinner" v-else-if="followedArtists.length > 0"></div>
<div class="no-artist" v-else> {{app.getLz('home.artistsFeed.noArtist')}}</div>

View file

@ -81,7 +81,7 @@
</div>
</div>
<mediaitem-list-item v-if="prefs.viewAs == 'list'" :show-duration="false" :show-meta-data="true"
:show-library-status="false" :item="item" v-for="item in currentSlice">
:show-library-status="false" v-bind:key="item.id" :item="item" v-for="item in currentSlice">
</mediaitem-list-item>
</div>
</div>

View file

@ -2,11 +2,11 @@
<div class="content-inner">
<h1 class="header-text">{{$root.getLz('term.recentlyAdded')}}</h1>
<div class="well itemContainer collection-list-square" v-if="itemSize == 'normal'">
<mediaitem-square v-for="item in items" :item="item"></mediaitem-square>
<mediaitem-square v-for="item in items" :item="item" v-bind:key="item.id"></mediaitem-square>
</div>
<div class="well itemContainer collection-list-square" v-else="itemSize == 'compact'">
<mediaitem-list-item :show-meta-data="true" :show-library-status="false" v-for="item in items"
:item="item"></mediaitem-list-item>
:item="item" v-bind:key="item.id"></mediaitem-list-item>
</div>
<div class="well itemContainer collection-list-square" v-show="loading">
<div class="spinner"></div>

View file

@ -84,12 +84,12 @@
<div v-if="library.songs.downloadState == 3">Library contains no songs.</div>
<div class="well" :key="1" v-if="prefs.size == 'compact'">
<mediaitem-list-item class-list="compact" :item="item" :parent="'librarysongs'" :index="index"
:show-meta-data="true" :show-library-status="false"
:show-meta-data="true" :show-library-status="false" v-bind:key="item.id"
v-for="(item, index) in currentSlice"></mediaitem-list-item>
</div>
<div class="well" :key="2" v-else>
<mediaitem-list-item :item="item" :parent="'librarysongs'" :index="index" :show-meta-data="true"
:show-library-status="false"
:show-library-status="false" v-bind:key="item.id"
v-for="(item, index) in currentSlice"></mediaitem-list-item>
</div>
</div>

View file

@ -5,7 +5,7 @@
</div>
</div>
<div class="madeforyou-body">
<mediaitem-square :item="item" v-for="item in madeforyou.data">
<mediaitem-square :item="item" v-for="item in madeforyou.data" v-bind:key="item.id">
</mediaitem-square>
</div>
</div>

View file

@ -153,7 +153,9 @@
<div class="blurb"></div>
</div>
<div class="oobe-footer">
<div class="btn-group">
<div class="md-btn" @click="app.appMode ='player'">{{ getLz("oobe.done") }}</div>
</div>
</div>
</div>
<div class="oobe-titlebar">

View file

@ -171,6 +171,7 @@
:showIndex="true"
:showIndexPlaylist="(data.attributes.playParams.kind ?? data.type ?? '').includes('playlist')"
:context-ext="buildContextMenu()"
v-bind:key="item.id"
v-for="(item,index) in data.relationships.tracks.data"></mediaitem-list-item>
</template>
<template v-else>
@ -183,6 +184,7 @@
:showIndex="true"
:showIndexPlaylist="(data.attributes.playParams.kind ?? data.type ?? '').includes('playlist')"
:context-ext="buildContextMenu()"
v-bind:key="item.id"
v-for="(item,index) in disc.tracks"></mediaitem-list-item>
</div>
@ -197,6 +199,7 @@
<div class="badge-container">
<div class="socialBadge"
:title="`${badge.attributes.name} - @${badge.attributes.handle}`"
v-bind:key="badge.id"
v-for="badge in itemBadges">
<mediaitem-artwork
:url="badge.attributes.artwork.url"
@ -443,6 +446,7 @@
return {
normal: [
{
icon: "./assets/feather/x-circle.svg",
name: app.getLz('action.removeFromPlaylist'),
action: () => {
self.remove()
@ -451,6 +455,7 @@
],
multiple: [
{
icon: "./assets/feather/x-circle.svg",
name: app.getLz('action.removeFromPlaylist'),
action: () => {
self.remove()

View file

@ -1,28 +1,76 @@
<script type="text/x-template" id="cider-radio">
<div class="content-inner">
<h1 class="header-text">{{ app.getLz('term.radio') }}</h1>
<h1 class="header-text">{{$root.getLz("term.radio")}}</h1>
<template v-if="data.relationships && data.relationships.tabs">
<template v-for="(recom,index) in data.relationships.tabs.data[0].relationships.children.data">
<div class="row">
<div class="col">
<h3>{{ app.getLz('term.personalStations') }}</h3>
<div class="col" v-if="recom.attributes.name != 'Chart Set'">
<h3>{{ recom.attributes.name ?? ""}}</h3>
</div>
<div class="col-auto cider-flex-center">
<button class="cd-btn-seeall" v-if="recom.attributes.name == 'Recently Played' && recent.length > 10"
@click="app.showCollection({ data: recent }, recom.attributes.name ?? '', 'listen_now')">
{{app.getLz('term.seeAll')}}
</button>
<template v-if="index != 0 && recom.relationships && ((recom.relationships.children && recom.relationships.children.data.length > 10) || (recom.relationships.contents && recom.relationships.contents.data.length > 10))">
<button class="cd-btn-seeall" v-if="recom.relationships.room"
@click="app.showRoom(recom.relationships.room?.data[0].href)">
{{app.getLz('term.seeAll')}}
</button>
<button class="cd-btn-seeall" v-else
@click="app.showCollection(recom.relationships.children ? recom.relationships.children : recom.relationships.contents, recom.attributes.name ?? '', 'listen_now')">
{{app.getLz('term.seeAll')}}
</button>
</template>
</div>
</div>
<!-- <mediaitem-square :kind="'385'" size="600"-->
<!-- :item="item ? (item.attributes.kind ? item : ((item.relationships && item.relationships.contents ) ? item.relationships.contents.data[0] : item)) : []"-->
<!-- :imagesize="800"-->
<!-- v-for="item of getFlattenedCategories()">-->
<mediaitem-square :item="item" v-for="item in radio.personal"></mediaitem-square>
<div class="row">
<div class="col">
<h3>{{ app.getLz('term.recentStations') }}</h3>
<div v-if="recom.attributes.name == 'Recently Played'">
<mediaitem-scroller-horizontal-mvview :imagesize="800"
:browsesp="index == 0|| (data.relationships.tabs.data[0].relationships.children.data[0].relationships == null && index === 1)"
:kind="recom.attributes.editorialElementKind"
:items="recent.limit(10)"></mediaitem-scroller-horizontal-mvview>
</div>
<template v-else-if="(recom.relationships != null && ((recom.relationships.children && recom.relationships.children.data) || (recom.relationships.contents && recom.relationships.contents.data)))">
<template v-if="index === 0|| (data.relationships.tabs.data[0].relationships.children.data[0].relationships == null && index === 1)">
<mediaitem-scroller-horizontal-mvview :imagesize="800"
:browsesp="index == 0|| (data.relationships.tabs.data[0].relationships.children.data[0].relationships == null && index === 1)"
:kind="recom.attributes.editorialElementKind"
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
<template v-else-if="(['327']).includes(recom.attributes.editorialElementKind)">
<div class="mediaitem-list-item__grid">
<listitem-horizontal :items="recom.relationships.contents.data.limit(20)">
</listitem-horizontal>
</div>
<mediaitem-square :item="station" v-for="station in radio.recent"></mediaitem-square>
<div class="row">
<div class="col">
<h3>{{ app.getLz('term.amLive') }}</h3>
</template>
<template v-else-if="(['385']).includes(recom.attributes.editorialElementKind)">
<mediaitem-scroller-horizontal-mvview :imagesize="800"
:kind="recom.attributes.editorialElementKind"
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-mvview>
</template>
<template v-else-if="recom.attributes.name == 'Chart Set'">
<!-- ignored -->
</template>
<template v-else-if="(['488']).includes(recom.attributes.editorialElementKind)">
<!-- ignored -->
</template>
<template v-else>
<mediaitem-scroller-horizontal-large
:items="recom.relationships.children ? recom.relationships.children.data.limit(10) : recom.relationships.contents.data.limit(10)"></mediaitem-scroller-horizontal-large>
</template>
</template>
<template v-else>
<template v-if="recom.attributes.links && recom.attributes.editorialElementKind.includes('391')">
<div class="grouping-container">
<button class="grouping-btn" @click="$root.goToGrouping(link.url)"
v-for="link in recom.attributes.links">{{ link.label }}
</button>
</div>
</div>
<mediaitem-square :item="station" v-for="station in radio.am"></mediaitem-square>
</template>
</template>
</template>
</template>
</div>
</script>
@ -33,59 +81,30 @@
data: function() {
return {
app: this.$root,
radio: { personal: [], recent: [], am: [] }
}
recent: []
};
},
async mounted() {
this.radio.personal = await this.getPersonalStations()
this.radio.recent = await this.getRecentStations()
this.radio.am = await this.getAmStations()
console.log(this.radio)
// this.getPersonalStations();
// this.getAmStations();
mounted() {
this.$root.getRadioPage();
// debugger
this.getRecentlyPlayed();
// debugger
},
methods: {
async getPersonalStations(attempts = 0) {
if (attempts > 3) {
return []
}
try {
return (await app.mk.api.v3.music(`/v1/catalog/${app.mk.api.v3.storefrontId}/stations`, {
"filter[identity]": "personal",
})).data.data
} catch (e) {
console.error(`Failed to get personal stations: ${e}`)
await this.getPersonalStations(attempts + 1)
}
},
async getRecentStations(attempts = 0) {
if (attempts > 3) {
return []
}
try {
return (await app.mk.api.v3.music(`/v1/me/recent/radio-stations`, {
getRecentlyPlayed: async function (next = null) {
const recent = await app.mk.api.v3.music(`${next ?? "/v1/me/recent/radio-stations"}`, {
"platform": "web",
"art[url]": "f",
l: app.mklang
})).data.data
} catch (e) {
console.error(`Failed to get recent stations: ${e}`)
await this.getRecentStations(attempts + 1)
}
},
async getAmStations(attempt = 0) {
if (attempt > 3) {
return []
}
try {
return (await app.mk.api.v3.music(`/v1/catalog/${app.mk.api.v3.storefrontId}/stations`, {
"filter[featured]": "apple-music-live-radio",
})).data.data
} catch (e) {
console.error(`Failed to get AM stations: ${e}`)
await this.getAmStations(attempt + 1)
}
},
}
})
console.debug(recent.data.data)
this.recent = this.recent.concat(recent.data.data);
if (recent.data.next) {
this.getRecentlyPlayed(recent.data.next);
}
}
}
});
</script>

View file

@ -23,8 +23,10 @@
<hr>
<div class="row">
<div class="col">
<h4>{{ convertToHours(loaded.attributes.listenTimeInMinutes) }}
{{$root.getLz('term.time.hours')}}</h4>
<h4 @click="hourshow = !hourshow">{{convertToHours(loaded.attributes.listenTimeInMinutes)}}
{{$root.getLz('term.time.hours')}}
{{hourshow ? "" : (loaded.attributes.listenTimeInMinutes % 60) }}
{{hourshow ? "" : $root.getLz('term.time.minutes')}}</h4>
<h4>{{ loaded.attributes.uniqueAlbumCount }} {{$root.getLz('term.uniqueAlbums')}}</h4>
<h4>{{ loaded.attributes.uniqueArtistCount }} {{$root.getLz('term.uniqueArtists')}}</h4>
<h4>{{ loaded.attributes.uniqueSongCount }} {{$root.getLz('term.uniqueSongs')}}</h4>
@ -101,6 +103,7 @@
loaded: {
id: -1
},
hourshow: true,
musicTypeGenre: ""
}
},

View file

@ -3,18 +3,22 @@
<div class="search-input-container fs-search" v-if="$root.appMode == 'fullscreen'">
<div class="search-input--icon"></div>
<input type="search" spellcheck="false" @focus="$root.search.showHints = true"
@blur="$root.setTimeout(()=>{$root.search.showHints = false}, 300)"
v-on:keyup.enter="$root.searchQuery();$root.search.showHints = false" @input="$root.getSearchHints()"
@blur="$root.setTimeout(()=>{if($root.hintscontext != true){$root.search.showHints = false} }, 300)"
v-on:keyup.enter="$root.searchQuery($root.search.hints[$root.search.cursor]?.content ?? $root.search.hints[$root.search.cursor]?.searchTerm ?? $root.search.term);$root.search.showHints = false; $root.search.showSearchView = true" @input="$root.getSearchHints()"
:placeholder="$root.getLz('term.search') + '...'" v-model="$root.search.term"
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 class="search-hint text-overflow-elipsis" v-for="(hint, index) in $root.search.hints.filter((a) => {return a.content == null})" :class="{active: ($root.search.cursor == index)}"
@click="$root.search.term = hint.searchTerm;$root.search.showHints = false;$root.searchQuery(hint.searchTerm)">
{{ hint.displayTerm }}
</button>
<template v-for="(item, position) in $root.search.hints.filter((a) => {return a.content != null})">
<mediaitem-smarthints :item="item.content" :position="position"> </mediaitem-smarthints>
</template>
</div>
</div>
</div>
<div class="btn-group searchToggle">
@ -29,16 +33,12 @@
$root.getLz("term.library") }}
</button>
</div>
<div v-if="search != null && search != [] && search.term != ''">
<div v-if="search != null && search != [] && search.term != '' && $root.search.showSearchView">
<template v-if="searchType == 'catalog'">
<h3>{{app.getLz('term.topResult')}}</h3>
<mediaitem-scroller-horizontal
:items="search.results[search.results.meta.results.order[0]]['data']"></mediaitem-scroller-horizontal>
:items="search?.results[search?.results?.meta?.results?.order[0]]?.data"></mediaitem-scroller-horizontal>
<div class="row">
<div v-else style="text-align: center">
<h3>{{app.getLz('error.noResults')}}</h3>
<p>{{app.getLz('error.noResults.description')}}</p>
</div>
<div class="col" v-if="search.results.song">
<div class="row">
<div class="col">
@ -55,6 +55,10 @@
</listitem-horizontal>
</div>
</div>
<div v-else style="text-align: center">
<h3>{{app.getLz('error.noResults')}}</h3>
<p>{{app.getLz('error.noResults.description')}}</p>
</div>
</div>
<template v-if="search.results['meta'] != null">
@ -131,21 +135,21 @@
<div v-if="categoriesReady || getCategories()">
<div>
<div class="col"
v-if="categoriesView != null && categoriesView != [] && categoriesView[0].attributes != null && categoriesView[0].attributes.title != null">
v-if="categoriesView != null && categoriesView != [] && categoriesView[0]?.attributes != null && categoriesView[0]?.attributes.title != null">
<h3>{{$root.getLz('home.recentlyPlayed')}}</h3>
<div class="mediaitem-list-item__grid">
<listitem-horizontal :items="recentlyPlayed.limit(10)">
</listitem-horizontal>
</div>
<!-- <mediaitem-square :kind="'385'" size="600" v-for="item in recentlyPlayed.limit(10)" :item="item" :imagesize="800"></mediaitem-square>-->
<h3>{{categoriesView[0].attributes.title.stringForDisplay ?? ""}}</h3>
<h3>{{categoriesView[0]?.attributes?.title?.stringForDisplay ?? ""}}</h3>
</div>
</div>
<div class="categories">
<mediaitem-square :kind="'385'" :imageformat="'bb'" size="600"
<mediaitem-square :kind="'385'" :imageformat="'bb'" size="600" :removeamtext="true"
:item="item ? (item.attributes.kind ? item : ((item.relationships && item.relationships.contents ) ? item.relationships.contents.data[0] : item)) : []"
:imagesize="800"
v-for="item of getFlattenedCategories()">
v-for="item of getFlattenedCategories()"/>
</div>
</div>
</div>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<rect x="0" y="0" width="1024" height="1024" style="fill:rgb(110,110,110);"/>
<g transform="matrix(6.05996,0,0,6.05996,189.003,209)">
<path d="M93.161,0.071C59.66,-1.043 32.22,11.314 32.22,11.314L32.2,74.023C28.789,72.669 24.641,72.348 20.428,73.372C11.345,75.579 5.397,83.192 7.143,90.379C8.889,97.566 17.667,101.604 26.749,99.398C35.313,97.317 41.087,90.429 40.256,83.626L40.256,36.771C40.256,36.771 59.66,29.987 84.829,28.286L84.829,63.135C81.455,61.843 77.386,61.55 73.25,62.555C64.167,64.761 58.219,72.374 59.965,79.562C61.71,86.749 70.488,90.786 79.571,88.58C87.502,86.653 93.042,80.603 93.158,74.316L93.161,74.32L93.161,0.071Z" style="fill-opacity:0.16;fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -507,6 +507,11 @@ input[type="range"].web-slider::-webkit-slider-runnable-track {
cursor: pointer;
}
.list-entry.artist-song {
padding: 5px;
width: 100%;
}
.list-entry-image {
--artwork: url("");
width: 64px;
@ -519,6 +524,11 @@ input[type="range"].web-slider::-webkit-slider-runnable-track {
box-shadow: var(--mediaItemShadow);
}
.artist-song .list-entry-image {
width: 45px;
height: 45px;
}
.list-entry-image.artist {
border-radius: 50%;
}
@ -586,6 +596,10 @@ input[type="range"].web-slider::-webkit-slider-runnable-track {
width: 100%;
}
.mediaitem-scroller-horizontal::-webkit-scrollbar {
display: none;
}
.album-placeholder {
height: 180px;
width: 180px;
@ -643,6 +657,10 @@ input[type="range"].web-slider::-webkit-slider-runnable-track {
overflow-y: hidden;
}
.search-tab-container::-webkit-scrollbar {
display: none;
}
.search-body-container {
position: relative;
width: 100%;
@ -704,7 +722,7 @@ input[type="range"].web-slider::-webkit-slider-runnable-track {
}
.albumpage-album-notes > .notes-preview {
height: 60px;
height: 48px;
overflow: hidden;
-webkit-mask-image: -webkit-gradient(linear, left 95%, left bottom, from(rgba(0, 0, 0, 1)), to(rgba(0, 0, 0, 0)));
}
@ -757,13 +775,13 @@ input[type="range"].web-slider::-webkit-slider-runnable-track {
}
.artist-header {
height: 400px;
/* height: 400px; */
width: 100%;
margin: 12px auto;
/* margin: 12px auto; */
display: flex;
justify-content: center;
align-items: center;
padding: 12px;
/* padding: 12px; */
flex-direction: column;
background: rgb(0 0 0 / 40%);
}

View file

@ -1,7 +1,7 @@
<script type="text/x-template" id="mediaitem-artwork">
<div class="mediaitem-artwork" :class="[{'rounded': (type == 'artists')}, classes]" :key="url"
v-observe-visibility="{callback: visibilityChanged}">
<img :src="getMediaItemArtwork(url, size, width)"
<img :src="getMediaItemArtwork(url, size, width)" :style="{ width: size + 'px'}"
decoding="async" loading="lazy"
class="mediaitem-artwork--img">
<!-- <div v-if="video && isVisible && getVideoPriority()" class="animatedartwork-view-box">
@ -86,6 +86,7 @@
this.isVisible = isVisible
},
getMediaItemArtwork(url, height = 64, width) {
try{
if (typeof url == "undefined" || url == "") {
return "./assets/MissingArtwork.svg"
}
@ -98,7 +99,9 @@
if (newurl.includes("900x516")) {
newurl = newurl.replace("900x516cc", "900x516sr").replace("900x516bb", "900x516sr");
}
return newurl
return newurl} catch (e){
return "./assets/MissingArtwork.svg"
}
},
}
});

View file

@ -213,7 +213,7 @@
}
switch (this.kind) {
case "385":
artwork = this.item.attributes.editorialArtwork.subscriptionHero.url
artwork = this.item.attributes?.editorialArtwork?.subscriptionHero?.url
break;
}
if (!includeUrl) {
@ -320,7 +320,7 @@
{
"icon": "./assets/feather/heart.svg",
"id": "love",
"name": "Love",
"name": app.getLz("action.love"),
"hidden": false,
"disabled": true,
"action": function() {
@ -331,7 +331,7 @@
"icon": "./assets/feather/heart.svg",
"id": "unlove",
"active": true,
"name": "Unlove",
"name": app.getLz("action.unlove"),
"hidden": true,
"action": function() {
app.unlove(self.item)
@ -340,7 +340,7 @@
{
"icon": "./assets/feather/thumbs-down.svg",
"id": "dislike",
"name": "Dislike",
"name": app.getLz("action.dislike"),
"hidden": false,
"disabled": true,
"action": function() {
@ -350,7 +350,7 @@
{
"icon": "./assets/feather/thumbs-down.svg",
"id": "undo_dislike",
"name": "Undo dislike",
"name": app.getLz("action.undoDislike"),
"active": true,
"hidden": true,
"action": function() {
@ -362,7 +362,7 @@
{
"icon": "./assets/feather/list.svg",
"id": "addToPlaylist",
"name": "Add to Playlist...",
"name": app.getLz("action.addToPlaylist") + " ...",
"action": function() {
app.promptAddToPlaylist()
}
@ -370,7 +370,7 @@
{
"id": "addToLibrary",
"icon": "./assets/feather/plus.svg",
"name": "Add to library",
"name": app.getLz("action.addToLibrary") + " ...",
"hidden": false,
"disabled": true,
"action": function() {
@ -383,7 +383,7 @@
{
"id": "removeFromLibrary",
"icon": "./assets/feather/x-circle.svg",
"name": "Remove from library",
"name": app.getLz("action.removeFromLibrary"),
"hidden": true,
"action": async function() {
console.log("remove");
@ -394,7 +394,7 @@
}
},
{
"name": "Play Next",
"name": app.getLz("action.playNext"),
"icon": "./assets/arrow-bend-up.svg",
"action": function() {
app.mk.playNext({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
@ -403,7 +403,7 @@
}
},
{
"name": "Play Later",
"name": app.getLz("action.playLater"),
"icon": "./assets/arrow-bend-down.svg",
"action": function() {
app.mk.playLater({ [self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id })
@ -413,7 +413,7 @@
},
{
"icon": "./assets/feather/share.svg",
"name": "Share",
"name": app.getLz("action.share"),
"action": function() {
self.app.copyToClipboard(self.item.attributes.url)
}

View file

@ -263,8 +263,8 @@
@click="trackSelect(song)">
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image" v-if="song.attributes.artwork"
:style="{'--artwork': getAlbumArtUrlList(song.attributes.artwork.url)}">
<div class="list-entry-image"
:style="{'--artwork': getAlbumArtUrlList(song.attributes?.artwork?.url ?? './assets/MissingArtwork.svg')}">
</div>
</div>
<div class="col flex-center">
@ -284,8 +284,8 @@
@click="trackSelect(song)">
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image" v-if="song.attributes.artwork"
:style="{'--artwork': getAlbumArtUrlList(song.attributes.artwork.url)}">
<div class="list-entry-image"
:style="{'--artwork': getAlbumArtUrlList(song.attributes.artwork?.url ?? './assets/MissingArtwork.svg' )}">
</div>
</div>
<div class="col flex-center">
@ -307,8 +307,8 @@
@click="showAlbum(album.id)">
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image" v-if="album.attributes.artwork"
:style="{'--artwork': getAlbumArtUrlList(album.attributes.artwork.url)}">
<div class="list-entry-image"
:style="{'--artwork': getAlbumArtUrlList(album.attributes?.artwork?.url ?? './assets/MissingArtwork.svg')}">
</div>
</div>
<div class="col flex-center">
@ -328,8 +328,8 @@
@click="showAlbum(album.id, true)">
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image" v-if="album.attributes.artwork"
:style="{'--artwork': getAlbumArtUrlList(album.attributes.artwork.url)}">
<div class="list-entry-image"
:style="{'--artwork': getAlbumArtUrlList(album.attributes?.artwork?.url ?? './assets/MissingArtwork.svg')}">
</div>
</div>
<div class="col flex-center">
@ -352,8 +352,7 @@
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image artist"
v-if="artist.attributes.artwork"
:style="{'--artwork': getAlbumArtUrlList(artist.attributes.artwork.url)}">
:style="{'--artwork': getAlbumArtUrlList(artist?.attributes?.artwork?.url ?? './assets/MissingArtwork.svg')}">
</div>
</div>
<div class="col flex-center">
@ -373,8 +372,7 @@
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image artist"
v-if="artist.attributes.artwork"
:style="{'--artwork': getAlbumArtUrlList(artist.attributes.artwork.url)}">
:style="{'--artwork': getAlbumArtUrlList(artist.attributes?.artwork?.url ?? './assets/MissingArtwork.svg')}">
</div>
</div>
<div class="col flex-center">
@ -396,8 +394,8 @@
@click="showPlaylist(playlist.attributes.playParams.id)">
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image" v-if="playlist.attributes.artwork"
:style="{'--artwork': getAlbumArtUrlList(playlist.attributes.artwork.url)}">
<div class="list-entry-image"
:style="{'--artwork': getAlbumArtUrlList(playlist.attributes?.artwork?.url ?? './assets/MissingArtwork.svg')}">
</div>
</div>
<div class="col flex-center">
@ -418,8 +416,8 @@
>
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image" v-if="playlist.attributes.artwork"
:style="{'--artwork': getAlbumArtUrlList(playlist.attributes.artwork.url)}">
<div class="list-entry-image"
:style="{'--artwork': getAlbumArtUrlList(playlist.attributes?.artwork?.url ?? './assets/MissingArtwork.svg')}">
</div>
</div>
<div class="col flex-center">
@ -452,7 +450,7 @@
<div class="col-auto flex-center" v-if="search.selected.attributes.artwork"
style="display:flex;align-items: center;">
<div class="list-entry-image"
:style="{'--artwork': getAlbumArtUrlList(search.selected.attributes.artwork.url)}">
:style="{'--artwork': getAlbumArtUrlList(search.selected.attributes?.artwork?.url ?? './assets/MissingArtwork.svg')}">
</div>
</div>
<div class="col flex-center" style="display:flex;align-items: center;">
@ -625,17 +623,35 @@
<div class="artist-header" v-if="artistPage.data.attributes['artwork']"
:style="getMediaPalette(artistPage.data.attributes)">
<div class="artist-header-portrait"
:style="{'--artwork': getAlbumArtUrlList(artistPage.data.attributes['artwork']['url'], 600)}">
:style="{'--artwork': getAlbumArtUrlList(artistPage.data.attributes['artwork']['url'] ?? './assets/MissingArtwork.svg', 600)}">
</div>
<h2>{{ artistPage.data.attributes["name"] }}</h2>
</div>
<div class="md-body artist-body">
<h2>Songs</h2>
<div class="song-scroller-horizontal">
<button v-for="song in artistPage.data.relationships['songs'].data"
<div class="list-entry artist-song" v-for="song in artistPage.data.relationships['songs'].data"
@click="trackSelect(song)">
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image"
:style="{'--artwork': getAlbumArtUrlList(song.attributes?.artwork?.url ?? './assets/MissingArtwork.svg', 45)}">
</div>
</div>
<div class="col flex-center">
<div class="list-entry-name">
{{ song.attributes.name }}
</div>
<div class="list-entry-artist">
{{ song.attributes.artistName }}
</div>
</div>
</div>
</div>
<!-- <button v-for="song in artistPage.data.relationships['songs'].data"
class="song-placeholder" @click="trackSelect(song)">
{{ song.attributes.name }}
</button>
</button> -->
</div>
<h2>Albums</h2>
<div class="mediaitem-scroller-horizontal">
@ -712,8 +728,8 @@
</div>
</div>
<div class="col-auto flex-center">
<div class="list-entry-image" v-if="song.item.attributes.artwork"
:style="{'--artwork': getAlbumArtUrlList(song.item.attributes.artwork.url)}">
<div class="list-entry-image"
:style="{'--artwork': getAlbumArtUrlList(song.item.attributes?.artwork?.url ?? './assets/MissingArtwork.svg')}">
</div>
</div>
<div class="col flex-center">
@ -807,7 +823,7 @@
<div class="album-body-container">
<div class="md-header">
<div class="albumpage-artwork"
:style="{'--artwork': getAlbumArtUrlList(albumPage.data.attributes['artwork'] ? albumPage.data.attributes['artwork']['url'] : '', 300)}">
:style="{'--artwork': getAlbumArtUrlList(albumPage.data.attributes['artwork'] ? albumPage.data.attributes['artwork']['url'] : './assets/MissingArtwork.svg', 300)}">
</div>
<div class="albumpage-album-name">
{{ albumPage.data.attributes["name"] }}
@ -853,8 +869,8 @@
@click="trackSelect(song)">
<div class="row">
<div class="col-auto flex-center">
<div class="list-entry-image" v-if="song.attributes.artwork"
:style="{'--artwork': getAlbumArtUrlList(song.attributes.artwork.url)}">
<div class="list-entry-image"
:style="{'--artwork': getAlbumArtUrlList(song.attributes?.artwork?.url ?? './assets/MissingArtwork.svg')}">
</div>
</div>
<div class="col flex-center">

View file

@ -262,29 +262,7 @@
ue.pop(), (ce.target = ue[ue.length - 1]);
}
var pe = function (e, t, n, r, i, o, a, s) {
(this.tag = e),
(this.data = t),
(this.children = n),
(this.text = r),
(this.elm = i),
(this.ns = void 0),
(this.context = o),
(this.fnContext = void 0),
(this.fnOptions = void 0),
(this.fnScopeId = void 0),
(this.key = t && t.key),
(this.componentOptions = a),
(this.componentInstance = void 0),
(this.parent = void 0),
(this.raw = !1),
(this.isStatic = !1),
(this.isRootInsert = !0),
(this.isComment = !1),
(this.isCloned = !1),
(this.isOnce = !1),
(this.asyncFactory = s),
(this.asyncMeta = void 0),
(this.isAsyncPlaceholder = !1);
(this.tag = e), (this.data = t), (this.children = n), (this.text = r), (this.elm = i), (this.ns = void 0), (this.context = o), (this.fnContext = void 0), (this.fnOptions = void 0), (this.fnScopeId = void 0), (this.key = t && t.key), (this.componentOptions = a), (this.componentInstance = void 0), (this.parent = void 0), (this.raw = !1), (this.isStatic = !1), (this.isRootInsert = !0), (this.isComment = !1), (this.isCloned = !1), (this.isOnce = !1), (this.asyncFactory = s), (this.asyncMeta = void 0), (this.isAsyncPlaceholder = !1);
},
de = { child: { configurable: !0 } };
(de.child.get = function () {
@ -696,8 +674,7 @@
}
function it(e, n, i, o, a, s) {
var c, u, l, f;
for (c in e)
(u = e[c]), (l = n[c]), (f = nt(c)), t(u) || (t(l) ? (t(u.fns) && (u = e[c] = rt(u, s)), r(f.once) && (u = e[c] = a(f.name, u, f.capture)), i(f.name, u, f.capture, f.passive, f.params)) : u !== l && ((l.fns = u), (e[c] = l)));
for (c in e) (u = e[c]), (l = n[c]), (f = nt(c)), t(u) || (t(l) ? (t(u.fns) && (u = e[c] = rt(u, s)), r(f.once) && (u = e[c] = a(f.name, u, f.capture)), i(f.name, u, f.capture, f.passive, f.params)) : u !== l && ((l.fns = u), (e[c] = l)));
for (c in n) t(e[c]) && o((f = nt(c)).name, n[c], f.capture);
}
function ot(e, i, o) {
@ -723,20 +700,7 @@
? (function e(o, a) {
var s = [];
var c, u, l, f;
for (c = 0; c < o.length; c++)
t((u = o[c])) ||
"boolean" == typeof u ||
((l = s.length - 1),
(f = s[l]),
Array.isArray(u)
? u.length > 0 && (ct((u = e(u, (a || "") + "_" + c))[0]) && ct(f) && ((s[l] = he(f.text + u[0].text)), u.shift()), s.push.apply(s, u))
: i(u)
? ct(f)
? (s[l] = he(f.text + u))
: "" !== u && s.push(he(u))
: ct(u) && ct(f)
? (s[l] = he(f.text + u.text))
: (r(o._isVList) && n(u.tag) && t(u.key) && n(a) && (u.key = "__vlist" + a + "_" + c + "__"), s.push(u)));
for (c = 0; c < o.length; c++) t((u = o[c])) || "boolean" == typeof u || ((l = s.length - 1), (f = s[l]), Array.isArray(u) ? u.length > 0 && (ct((u = e(u, (a || "") + "_" + c))[0]) && ct(f) && ((s[l] = he(f.text + u[0].text)), u.shift()), s.push.apply(s, u)) : i(u) ? (ct(f) ? (s[l] = he(f.text + u)) : "" !== u && s.push(he(u))) : ct(u) && ct(f) ? (s[l] = he(f.text + u.text)) : (r(o._isVList) && n(u.tag) && t(u.key) && n(a) && (u.key = "__vlist" + a + "_" + c + "__"), s.push(u)));
return s;
})(e)
: void 0;
@ -1166,8 +1130,7 @@
var u, l;
if ("string" == typeof i) {
var f;
(l = (e.$vnode && e.$vnode.ns) || F.getTagNamespace(i)),
(u = F.isReservedTag(i) ? new pe(F.parsePlatformTagName(i), a, s, void 0, void 0, e) : (a && a.pre) || !n((f = Le(e.$options, "components", i))) ? new pe(i, a, s, void 0, void 0, e) : Mt(f, a, e, s, i));
(l = (e.$vnode && e.$vnode.ns) || F.getTagNamespace(i)), (u = F.isReservedTag(i) ? new pe(F.parsePlatformTagName(i), a, s, void 0, void 0, e) : (a && a.pre) || !n((f = Le(e.$options, "components", i))) ? new pe(i, a, s, void 0, void 0, e) : Mt(f, a, e, s, i));
} else u = Mt(i, a, e, s);
return Array.isArray(u)
? u
@ -1862,9 +1825,7 @@
(function (e) {
I.forEach(function (t) {
e[t] = function (e, n) {
return n
? ("component" === t && s(n) && ((n.name = n.name || e), (n = this.options._base.extend(n))), "directive" === t && "function" == typeof n && (n = { bind: n, update: n }), (this.options[t + "s"][e] = n), n)
: this.options[t + "s"][e];
return n ? ("component" === t && s(n) && ((n.name = n.name || e), (n = this.options._base.extend(n))), "directive" === t && "function" == typeof n && (n = { bind: n, update: n }), (this.options[t + "s"][e] = n), n) : this.options[t + "s"][e];
};
});
})(e);
@ -1887,9 +1848,7 @@
Mn = function (e, t) {
return Bn(t) || "false" === t ? "false" : "contenteditable" === e && In(t) ? t : "true";
},
Fn = p(
"allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible"
),
Fn = p("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible"),
Pn = "http://www.w3.org/1999/xlink",
Rn = function (e) {
return ":" === e.charAt(5) && "xlink" === e.slice(0, 5);
@ -2109,19 +2068,7 @@
}
}
function hr(e, t, n, r) {
r || e.tagName.indexOf("-") > -1
? mr(e, t, n)
: Fn(t)
? Bn(n)
? e.removeAttribute(t)
: ((n = "allowfullscreen" === t && "EMBED" === e.tagName ? "true" : t), e.setAttribute(t, n))
: Ln(t)
? e.setAttribute(t, Mn(t, n))
: Rn(t)
? Bn(n)
? e.removeAttributeNS(Pn, Hn(t))
: e.setAttributeNS(Pn, t, n)
: mr(e, t, n);
r || e.tagName.indexOf("-") > -1 ? mr(e, t, n) : Fn(t) ? (Bn(n) ? e.removeAttribute(t) : ((n = "allowfullscreen" === t && "EMBED" === e.tagName ? "true" : t), e.setAttribute(t, n))) : Ln(t) ? e.setAttribute(t, Mn(t, n)) : Rn(t) ? (Bn(n) ? e.removeAttributeNS(Pn, Hn(t)) : e.setAttributeNS(Pn, t, n)) : mr(e, t, n);
}
function mr(e, t, n) {
if (Bn(n)) e.removeAttribute(t);
@ -2264,15 +2211,7 @@
}
function Mr(t, n, r, i, o, a, s, c) {
var u;
(i = i || e).right
? c
? (n = "(" + n + ")==='click'?'contextmenu':(" + n + ")")
: "click" === n && ((n = "contextmenu"), delete i.right)
: i.middle && (c ? (n = "(" + n + ")==='click'?'mouseup':(" + n + ")") : "click" === n && (n = "mouseup")),
i.capture && (delete i.capture, (n = Ir("!", n, c))),
i.once && (delete i.once, (n = Ir("~", n, c))),
i.passive && (delete i.passive, (n = Ir("&", n, c))),
i.native ? (delete i.native, (u = t.nativeEvents || (t.nativeEvents = {}))) : (u = t.events || (t.events = {}));
(i = i || e).right ? (c ? (n = "(" + n + ")==='click'?'contextmenu':(" + n + ")") : "click" === n && ((n = "contextmenu"), delete i.right)) : i.middle && (c ? (n = "(" + n + ")==='click'?'mouseup':(" + n + ")") : "click" === n && (n = "mouseup")), i.capture && (delete i.capture, (n = Ir("!", n, c))), i.once && (delete i.once, (n = Ir("~", n, c))), i.passive && (delete i.passive, (n = Ir("&", n, c))), i.native ? (delete i.native, (u = t.nativeEvents || (t.nativeEvents = {}))) : (u = t.events || (t.events = {}));
var l = Hr({ value: r.trim(), dynamic: c }, s);
i !== e && (l.modifiers = i);
var f = u[n];
@ -2558,9 +2497,7 @@
Oi = "transitionend",
Si = "animation",
Ti = "animationend";
Ci &&
(void 0 === window.ontransitionend && void 0 !== window.onwebkittransitionend && ((Ai = "WebkitTransition"), (Oi = "webkitTransitionEnd")),
void 0 === window.onanimationend && void 0 !== window.onwebkitanimationend && ((Si = "WebkitAnimation"), (Ti = "webkitAnimationEnd")));
Ci && (void 0 === window.ontransitionend && void 0 !== window.onwebkittransitionend && ((Ai = "WebkitTransition"), (Oi = "webkitTransitionEnd")), void 0 === window.onanimationend && void 0 !== window.onwebkitanimationend && ((Si = "WebkitAnimation"), (Ti = "webkitAnimationEnd")));
var Ni = V
? window.requestAnimationFrame
? window.requestAnimationFrame.bind(window)
@ -2638,30 +2575,7 @@
n(i._leaveCb) && ((i._leaveCb.cancelled = !0), i._leaveCb());
var a = $i(e.data.transition);
if (!t(a) && !n(i._enterCb) && 1 === i.nodeType) {
for (
var s = a.css,
c = a.type,
u = a.enterClass,
l = a.enterToClass,
p = a.enterActiveClass,
d = a.appearClass,
v = a.appearToClass,
h = a.appearActiveClass,
m = a.beforeEnter,
y = a.enter,
g = a.afterEnter,
_ = a.enterCancelled,
b = a.beforeAppear,
$ = a.appear,
w = a.afterAppear,
C = a.appearCancelled,
x = a.duration,
k = Zt,
A = Zt.$vnode;
A && A.parent;
)
(k = A.context), (A = A.parent);
for (var s = a.css, c = a.type, u = a.enterClass, l = a.enterToClass, p = a.enterActiveClass, d = a.appearClass, v = a.appearToClass, h = a.appearActiveClass, m = a.beforeEnter, y = a.enter, g = a.afterEnter, _ = a.enterCancelled, b = a.beforeAppear, $ = a.appear, w = a.afterAppear, C = a.appearCancelled, x = a.duration, k = Zt, A = Zt.$vnode; A && A.parent; ) (k = A.context), (A = A.parent);
var O = !k._isMounted || !e.isRootInsert;
if (!O || $ || "" === $) {
var S = O && d ? d : u,
@ -2786,11 +2700,7 @@
var f = e.data,
p = e.children,
m = e.tag;
n(m)
? ((e.elm = e.ns ? u.createElementNS(e.ns, m) : u.createElement(m, e)), g(e), h(e, p, t), n(f) && y(e, t), v(i, e.elm, o))
: r(e.isComment)
? ((e.elm = u.createComment(e.text)), v(i, e.elm, o))
: ((e.elm = u.createTextNode(e.text)), v(i, e.elm, o));
n(m) ? ((e.elm = e.ns ? u.createElementNS(e.ns, m) : u.createElement(m, e)), g(e), h(e, p, t), n(f) && y(e, t), v(i, e.elm, o)) : r(e.isComment) ? ((e.elm = u.createComment(e.text)), v(i, e.elm, o)) : ((e.elm = u.createTextNode(e.text)), v(i, e.elm, o));
}
}
function d(e, t) {
@ -2894,9 +2804,7 @@
? (x(h, b, o, i, y), w && u.insertBefore(e, h.elm, u.nextSibling(m.elm)), (h = r[++p]), (b = i[--y]))
: or(m, g)
? (x(m, g, o, i, d), w && u.insertBefore(e, m.elm, h.elm), (m = r[--v]), (g = i[++d]))
: (t(s) && (s = ar(r, p, v)),
t((c = n(g.key) ? s[g.key] : C(g, r, p, v))) ? f(g, o, e, h.elm, !1, i, d) : or((l = r[c]), g) ? (x(l, g, o, i, d), (r[c] = void 0), w && u.insertBefore(e, l.elm, h.elm)) : f(g, o, e, h.elm, !1, i, d),
(g = i[++d]));
: (t(s) && (s = ar(r, p, v)), t((c = n(g.key) ? s[g.key] : C(g, r, p, v))) ? f(g, o, e, h.elm, !1, i, d) : or((l = r[c]), g) ? (x(l, g, o, i, d), (r[c] = void 0), w && u.insertBefore(e, l.elm, h.elm)) : f(g, o, e, h.elm, !1, i, d), (g = i[++d]));
p > v ? _(e, t(i[y + 1]) ? null : i[y + 1].elm, i, d, y, o) : d > y && $(r, p, v);
})(p, h, y, o, l)
: n(y)
@ -3015,8 +2923,7 @@
})
: Ji(e, t, n.context),
(e._vOptions = [].map.call(e.options, Zi)))
: ("textarea" === n.tag || Yn(e.type)) &&
((e._vModifiers = t.modifiers), t.modifiers.lazy || (e.addEventListener("compositionstart", Gi), e.addEventListener("compositionend", Xi), e.addEventListener("change", Xi), W && (e.vmodel = !0)));
: ("textarea" === n.tag || Yn(e.type)) && ((e._vModifiers = t.modifiers), t.modifiers.lazy || (e.addEventListener("compositionstart", Gi), e.addEventListener("compositionend", Xi), e.addEventListener("change", Xi), W && (e.vmodel = !0)));
},
componentUpdated: function (e, t, n) {
if ("select" === n.tag) {
@ -3378,9 +3285,7 @@
},
$o = p("area,base,br,col,embed,frame,hr,img,input,isindex,keygen,link,meta,param,source,track,wbr"),
wo = p("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source"),
Co = p(
"address,article,aside,base,blockquote,body,caption,col,colgroup,dd,details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,title,tr,track"
),
Co = p("address,article,aside,base,blockquote,body,caption,col,colgroup,dd,details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,title,tr,track"),
xo = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,
ko = /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,
Ao = "[a-zA-Z_][\\-\\.0-9_a-zA-Z" + P.source + "]*",
@ -3782,14 +3687,7 @@
for (t = 0, n = u.length; t < n; t++)
if (((r = i = u[t].name), (o = u[t].value), Xo.test(r)))
if (((e.hasBindings = !0), (a = ma(r.replace(Xo, ""))) && (r = r.replace(ia, "")), ra.test(r)))
(r = r.replace(ra, "")),
(o = Or(o)),
(c = ta.test(r)) && (r = r.slice(1, -1)),
a &&
(a.prop && !c && "innerHtml" === (r = b(r)) && (r = "innerHTML"),
a.camel && !c && (r = b(r)),
a.sync && ((s = Ur(o, "$event")), c ? Mr(e, '"update:"+(' + r + ")", s, null, !1, 0, u[t], !0) : (Mr(e, "update:" + b(r), s, null, !1, 0, u[t]), C(r) !== b(r) && Mr(e, "update:" + C(r), s, null, !1, 0, u[t])))),
(a && a.prop) || (!e.component && Wo(e.tag, e.attrsMap.type, r)) ? Er(e, r, o, u[t], c) : jr(e, r, o, u[t], c);
(r = r.replace(ra, "")), (o = Or(o)), (c = ta.test(r)) && (r = r.slice(1, -1)), a && (a.prop && !c && "innerHtml" === (r = b(r)) && (r = "innerHTML"), a.camel && !c && (r = b(r)), a.sync && ((s = Ur(o, "$event")), c ? Mr(e, '"update:"+(' + r + ")", s, null, !1, 0, u[t], !0) : (Mr(e, "update:" + b(r), s, null, !1, 0, u[t]), C(r) !== b(r) && Mr(e, "update:" + C(r), s, null, !1, 0, u[t])))), (a && a.prop) || (!e.component && Wo(e.tag, e.attrsMap.type, r)) ? Er(e, r, o, u[t], c) : jr(e, r, o, u[t], c);
else if (Go.test(r)) (r = r.replace(Go, "")), (c = ta.test(r)) && (r = r.slice(1, -1)), Mr(e, r, o, a, !1, 0, u[t], c);
else {
var l = (r = r.replace(Xo, "")).match(na),
@ -3884,8 +3782,7 @@
if (e.component) return Br(e, r, i), !1;
if ("select" === o)
!(function (e, t, n) {
var r =
'var $$selectedVal = Array.prototype.filter.call($event.target.options,function(o){return o.selected}).map(function(o){var val = "_value" in o ? o._value : o.value;return ' + (n && n.number ? "_n(val)" : "val") + "});";
var r = 'var $$selectedVal = Array.prototype.filter.call($event.target.options,function(o){return o.selected}).map(function(o){var val = "_value" in o ? o._value : o.value;return ' + (n && n.number ? "_n(val)" : "val") + "});";
(r = r + " " + Ur(t, "$event.target.multiple ? $$selectedVal : $$selectedVal[0]")), Mr(e, "change", r, null, !0);
})(e, r, i);
else if ("input" === o && "checkbox" === a)
@ -3894,28 +3791,7 @@
i = Fr(e, "value") || "null",
o = Fr(e, "true-value") || "true",
a = Fr(e, "false-value") || "false";
Er(e, "checked", "Array.isArray(" + t + ")?_i(" + t + "," + i + ")>-1" + ("true" === o ? ":(" + t + ")" : ":_q(" + t + "," + o + ")")),
Mr(
e,
"change",
"var $$a=" +
t +
",$$el=$event.target,$$c=$$el.checked?(" +
o +
"):(" +
a +
");if(Array.isArray($$a)){var $$v=" +
(r ? "_n(" + i + ")" : i) +
",$$i=_i($$a,$$v);if($$el.checked){$$i<0&&(" +
Ur(t, "$$a.concat([$$v])") +
")}else{$$i>-1&&(" +
Ur(t, "$$a.slice(0,$$i).concat($$a.slice($$i+1))") +
")}}else{" +
Ur(t, "$$c") +
"}",
null,
!0
);
Er(e, "checked", "Array.isArray(" + t + ")?_i(" + t + "," + i + ")>-1" + ("true" === o ? ":(" + t + ")" : ":_q(" + t + "," + o + ")")), Mr(e, "change", "var $$a=" + t + ",$$el=$event.target,$$c=$$el.checked?(" + o + "):(" + a + ");if(Array.isArray($$a)){var $$v=" + (r ? "_n(" + i + ")" : i) + ",$$i=_i($$a,$$v);if($$el.checked){$$i<0&&(" + Ur(t, "$$a.concat([$$v])") + ")}else{$$i>-1&&(" + Ur(t, "$$a.slice(0,$$i).concat($$a.slice($$i+1))") + ")}}else{" + Ur(t, "$$c") + "}", null, !0);
})(e, r, i);
else if ("input" === o && "radio" === a)
!(function (e, t, n) {
@ -4243,19 +4119,7 @@
for (r = 0, i = n.length; r < i; r++) {
(o = n[r]), (a = !0);
var u = t.directives[o.name];
u && (a = !!u(e, o, t.warn)),
a &&
((c = !0),
(s +=
'{name:"' +
o.name +
'",rawName:"' +
o.rawName +
'"' +
(o.value ? ",value:(" + o.value + "),expression:" + JSON.stringify(o.value) : "") +
(o.arg ? ",arg:" + (o.isDynamicArg ? o.arg : '"' + o.arg + '"') : "") +
(o.modifiers ? ",modifiers:" + JSON.stringify(o.modifiers) : "") +
"},"));
u && (a = !!u(e, o, t.warn)), a && ((c = !0), (s += '{name:"' + o.name + '",rawName:"' + o.rawName + '"' + (o.value ? ",value:(" + o.value + "),expression:" + JSON.stringify(o.value) : "") + (o.arg ? ",arg:" + (o.isDynamicArg ? o.arg : '"' + o.arg + '"') : "") + (o.modifiers ? ",modifiers:" + JSON.stringify(o.modifiers) : "") + "},"));
}
if (c) return s.slice(0, -1) + "]";
})(e, t);
@ -4408,9 +4272,7 @@
function Ya(e) {
return e.replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
}
new RegExp(
"\\b" + "do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,super,throw,while,yield,delete,export,import,return,switch,default,extends,finally,continue,debugger,function,arguments".split(",").join("\\b|\\b") + "\\b"
);
new RegExp("\\b" + "do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,super,throw,while,yield,delete,export,import,return,switch,default,extends,finally,continue,debugger,function,arguments".split(",").join("\\b|\\b") + "\\b");
function Qa(e, t) {
try {
return new Function(e);

View file

@ -6,6 +6,7 @@
"allowJs": true,
"noImplicitAny": true,
"strict": true,
"inlineSources": true,
"sourceMap": true,
"outDir": "./build",
"baseUrl": ".",