mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
add experimental ytmusic playback for external songs
This commit is contained in:
@@ -135,6 +135,7 @@
|
||||
"string-to-color": "^2.2.2",
|
||||
"wavesurfer.js": "^7.11.1",
|
||||
"ws": "^8.18.2",
|
||||
"ytmusic-api": "^5.3.0",
|
||||
"zod": "^3.22.3",
|
||||
"zustand": "^5.0.5"
|
||||
},
|
||||
|
||||
Generated
+146
-1
@@ -218,6 +218,9 @@ importers:
|
||||
ws:
|
||||
specifier: ^8.18.2
|
||||
version: 8.18.2
|
||||
ytmusic-api:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
zod:
|
||||
specifier: ^3.22.3
|
||||
version: 3.25.76
|
||||
@@ -892,53 +895,106 @@ packages:
|
||||
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@biomejs/biome@1.8.3':
|
||||
resolution: {integrity: sha512-/uUV3MV+vyAczO+vKrPdOW0Iaet7UnJMU4bNMinggGJTAnBPjCoLEYcyYtYHNnUNYlv4xZMH6hVIQCAozq8d5w==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
hasBin: true
|
||||
|
||||
'@biomejs/biome@1.9.4':
|
||||
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
hasBin: true
|
||||
|
||||
'@biomejs/cli-darwin-arm64@1.8.3':
|
||||
resolution: {integrity: sha512-9DYOjclFpKrH/m1Oz75SSExR8VKvNSSsLnVIqdnKexj6NwmiMlKk94Wa1kZEdv6MCOHGHgyyoV57Cw8WzL5n3A==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@biomejs/cli-darwin-arm64@1.9.4':
|
||||
resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@biomejs/cli-darwin-x64@1.8.3':
|
||||
resolution: {integrity: sha512-UeW44L/AtbmOF7KXLCoM+9PSgPo0IDcyEUfIoOXYeANaNXXf9mLUwV1GeF2OWjyic5zj6CnAJ9uzk2LT3v/wAw==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@biomejs/cli-darwin-x64@1.9.4':
|
||||
resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@biomejs/cli-linux-arm64-musl@1.8.3':
|
||||
resolution: {integrity: sha512-9yjUfOFN7wrYsXt/T/gEWfvVxKlnh3yBpnScw98IF+oOeCYb5/b/+K7YNqKROV2i1DlMjg9g/EcN9wvj+NkMuQ==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-arm64-musl@1.9.4':
|
||||
resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-arm64@1.8.3':
|
||||
resolution: {integrity: sha512-fed2ji8s+I/m8upWpTJGanqiJ0rnlHOK3DdxsyVLZQ8ClY6qLuPc9uehCREBifRJLl/iJyQpHIRufLDeotsPtw==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-arm64@1.9.4':
|
||||
resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@1.8.3':
|
||||
resolution: {integrity: sha512-UHrGJX7PrKMKzPGoEsooKC9jXJMa28TUSMjcIlbDnIO4EAavCoVmNQaIuUSH0Ls2mpGMwUIf+aZJv657zfWWjA==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@1.9.4':
|
||||
resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-x64@1.8.3':
|
||||
resolution: {integrity: sha512-I8G2QmuE1teISyT8ie1HXsjFRz9L1m5n83U1O6m30Kw+kPMPSKjag6QGUn+sXT8V+XWIZxFFBoTDEDZW2KPDDw==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-x64@1.9.4':
|
||||
resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-win32-arm64@1.8.3':
|
||||
resolution: {integrity: sha512-J+Hu9WvrBevfy06eU1Na0lpc7uR9tibm9maHynLIoAjLZpQU3IW+OKHUtyL8p6/3pT2Ju5t5emReeIS2SAxhkQ==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@biomejs/cli-win32-arm64@1.9.4':
|
||||
resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@biomejs/cli-win32-x64@1.8.3':
|
||||
resolution: {integrity: sha512-/PJ59vA1pnQeKahemaQf4Nyj7IKUvGQSc3Ze1uIGi+Wvr1xF7rGobSrAAG01T/gUDG21vkDsZYM03NAmPiVkqg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@biomejs/cli-win32-x64@1.9.4':
|
||||
resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
@@ -3365,7 +3421,7 @@ packages:
|
||||
glob@8.1.0:
|
||||
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
|
||||
engines: {node: '>=12'}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
|
||||
global-agent@3.0.0:
|
||||
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
|
||||
@@ -4553,6 +4609,9 @@ packages:
|
||||
proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
|
||||
psl@1.15.0:
|
||||
resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
|
||||
|
||||
pump@3.0.2:
|
||||
resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==}
|
||||
|
||||
@@ -4568,6 +4627,9 @@ packages:
|
||||
resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
querystringify@2.2.0:
|
||||
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
|
||||
|
||||
queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
@@ -4813,6 +4875,9 @@ packages:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
requires-port@1.0.0:
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
|
||||
resedit@1.7.2:
|
||||
resolution: {integrity: sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==}
|
||||
engines: {node: '>=12', npm: '>=6'}
|
||||
@@ -5446,6 +5511,10 @@ packages:
|
||||
resolution: {integrity: sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
tough-cookie@4.1.4:
|
||||
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tough-cookie@5.1.2:
|
||||
resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -5570,6 +5639,10 @@ packages:
|
||||
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
|
||||
universalify@0.2.0:
|
||||
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
|
||||
universalify@2.0.1:
|
||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
@@ -5596,6 +5669,9 @@ packages:
|
||||
uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
||||
url-parse@1.5.10:
|
||||
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
|
||||
|
||||
use-callback-ref@1.3.3:
|
||||
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -5943,6 +6019,9 @@ packages:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
ytmusic-api@5.3.0:
|
||||
resolution: {integrity: sha512-RETSp5V5Cj1OT7N4HuEpehpq/YdKlKdXbYSSYH1ks8VBN1WRRpy2CFFwZVO+kIx7nuufBqsXN4B6/nxk4GNKDw==}
|
||||
|
||||
zod-validation-error@4.0.2:
|
||||
resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
@@ -6704,6 +6783,17 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@biomejs/biome@1.8.3':
|
||||
optionalDependencies:
|
||||
'@biomejs/cli-darwin-arm64': 1.8.3
|
||||
'@biomejs/cli-darwin-x64': 1.8.3
|
||||
'@biomejs/cli-linux-arm64': 1.8.3
|
||||
'@biomejs/cli-linux-arm64-musl': 1.8.3
|
||||
'@biomejs/cli-linux-x64': 1.8.3
|
||||
'@biomejs/cli-linux-x64-musl': 1.8.3
|
||||
'@biomejs/cli-win32-arm64': 1.8.3
|
||||
'@biomejs/cli-win32-x64': 1.8.3
|
||||
|
||||
'@biomejs/biome@1.9.4':
|
||||
optionalDependencies:
|
||||
'@biomejs/cli-darwin-arm64': 1.9.4
|
||||
@@ -6715,27 +6805,51 @@ snapshots:
|
||||
'@biomejs/cli-win32-arm64': 1.9.4
|
||||
'@biomejs/cli-win32-x64': 1.9.4
|
||||
|
||||
'@biomejs/cli-darwin-arm64@1.8.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-darwin-arm64@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-darwin-x64@1.8.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-darwin-x64@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-arm64-musl@1.8.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-arm64-musl@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-arm64@1.8.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-arm64@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@1.8.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-x64@1.8.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-x64@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-win32-arm64@1.8.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-win32-arm64@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-win32-x64@1.8.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-win32-x64@1.9.4':
|
||||
optional: true
|
||||
|
||||
@@ -10631,6 +10745,10 @@ snapshots:
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
psl@1.15.0:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
pump@3.0.2:
|
||||
dependencies:
|
||||
end-of-stream: 1.4.4
|
||||
@@ -10646,6 +10764,8 @@ snapshots:
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
|
||||
querystringify@2.2.0: {}
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
quick-lru@5.1.1: {}
|
||||
@@ -10896,6 +11016,8 @@ snapshots:
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
requires-port@1.0.0: {}
|
||||
|
||||
resedit@1.7.2:
|
||||
dependencies:
|
||||
pe-library: 0.4.1
|
||||
@@ -11624,6 +11746,13 @@ snapshots:
|
||||
dependencies:
|
||||
streamx: 2.22.0
|
||||
|
||||
tough-cookie@4.1.4:
|
||||
dependencies:
|
||||
psl: 1.15.0
|
||||
punycode: 2.3.1
|
||||
universalify: 0.2.0
|
||||
url-parse: 1.5.10
|
||||
|
||||
tough-cookie@5.1.2:
|
||||
dependencies:
|
||||
tldts: 6.1.86
|
||||
@@ -11747,6 +11876,8 @@ snapshots:
|
||||
|
||||
universalify@0.1.2: {}
|
||||
|
||||
universalify@0.2.0: {}
|
||||
|
||||
universalify@2.0.1: {}
|
||||
|
||||
unzip-crx-3@0.2.0:
|
||||
@@ -11773,6 +11904,11 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
url-parse@1.5.10:
|
||||
dependencies:
|
||||
querystringify: 2.2.0
|
||||
requires-port: 1.0.0
|
||||
|
||||
use-callback-ref@1.3.3(@types/react@19.2.5)(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
@@ -12169,6 +12305,15 @@ snapshots:
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
ytmusic-api@5.3.0:
|
||||
dependencies:
|
||||
'@biomejs/biome': 1.8.3
|
||||
axios: 1.13.2
|
||||
tough-cookie: 4.1.4
|
||||
zod: 3.25.76
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
zod-validation-error@4.0.2(zod@3.25.76):
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
@@ -4,3 +4,4 @@ import './player';
|
||||
import './remote';
|
||||
import './settings';
|
||||
import './discord-rpc';
|
||||
import './youtube';
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import YTMusic from 'ytmusic-api';
|
||||
|
||||
let youtubeApi: InstanceType<typeof YTMusic> | null = null;
|
||||
|
||||
const getYoutubeApi = async (): Promise<InstanceType<typeof YTMusic>> => {
|
||||
if (!youtubeApi) {
|
||||
youtubeApi = new YTMusic();
|
||||
await youtubeApi.initialize();
|
||||
}
|
||||
return youtubeApi;
|
||||
};
|
||||
|
||||
ipcMain.handle('youtube-search', async (_event, query: string) => {
|
||||
const api = await getYoutubeApi();
|
||||
const results = await api.search(query);
|
||||
return results;
|
||||
});
|
||||
@@ -11,6 +11,7 @@ import { mpris } from './mpris';
|
||||
import { mpvPlayer, mpvPlayerListener } from './mpv-player';
|
||||
import { remote } from './remote';
|
||||
import { utils } from './utils';
|
||||
import { youtube } from './youtube';
|
||||
|
||||
// Custom APIs for renderer
|
||||
const api = {
|
||||
@@ -25,6 +26,7 @@ const api = {
|
||||
mpvPlayerListener,
|
||||
remote,
|
||||
utils,
|
||||
youtube,
|
||||
};
|
||||
|
||||
export type PreloadApi = typeof api;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
const search = (query: string) => {
|
||||
return ipcRenderer.invoke('youtube-search', query);
|
||||
};
|
||||
|
||||
export const youtube = {
|
||||
search,
|
||||
};
|
||||
|
||||
export type Youtube = typeof youtube;
|
||||
@@ -3,11 +3,9 @@ import memoize from 'lodash/memoize';
|
||||
import {
|
||||
IArtist,
|
||||
IBrowseReleasesResult,
|
||||
IMedium,
|
||||
IRelation,
|
||||
IRelease,
|
||||
IReleaseGroup,
|
||||
ITrack,
|
||||
IWork,
|
||||
MusicBrainzApi,
|
||||
} from 'musicbrainz-api';
|
||||
@@ -15,14 +13,17 @@ import {
|
||||
import packageJson from '../../../../../package.json';
|
||||
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getImageUrl } from '/@/renderer/features/musicbrainz/utils';
|
||||
import {
|
||||
collectWorksFromRelease,
|
||||
getImageUrl,
|
||||
normalizeReleaseToAlbum,
|
||||
} from '/@/renderer/features/musicbrainz/utils';
|
||||
import {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
LibraryItem,
|
||||
RelatedArtist,
|
||||
ServerType,
|
||||
Song,
|
||||
} from '/@/shared/types/domain-types';
|
||||
|
||||
export const musicbrainzApi = new MusicBrainzApi({
|
||||
@@ -33,6 +34,8 @@ export const musicbrainzApi = new MusicBrainzApi({
|
||||
|
||||
const CACHE_TIME = 1000 * 60 * 5;
|
||||
|
||||
export type IRelationWithWork = IRelation & { work?: IWork };
|
||||
|
||||
export type MusicBrainzArtistSelectMeta = {
|
||||
albumArtist: AlbumArtist;
|
||||
albums?: Album[];
|
||||
@@ -40,8 +43,6 @@ export type MusicBrainzArtistSelectMeta = {
|
||||
prioritizeCountries?: string[];
|
||||
};
|
||||
|
||||
type IRelationWithWork = IRelation & { work?: IWork };
|
||||
|
||||
const artistSelect = memoize(
|
||||
({
|
||||
data,
|
||||
@@ -222,27 +223,6 @@ const artistSelect = memoize(
|
||||
},
|
||||
);
|
||||
|
||||
function collectWorksFromRelease(release: IRelease): IWork[] {
|
||||
const works: IWork[] = [];
|
||||
const seenIds = new Set<string>();
|
||||
|
||||
for (const medium of release.media ?? []) {
|
||||
for (const track of medium.tracks ?? []) {
|
||||
const recording = track.recording;
|
||||
const relations = (recording as { relations?: IRelationWithWork[] })?.relations ?? [];
|
||||
for (const rel of relations) {
|
||||
const work = (rel as IRelationWithWork).work;
|
||||
if (work?.id && !seenIds.has(work.id)) {
|
||||
seenIds.add(work.id);
|
||||
works.push(work);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return works;
|
||||
}
|
||||
|
||||
async function fetchMbzReleasesByArtistId(mbzArtistId: string): Promise<IBrowseReleasesResult> {
|
||||
const PAGE_SIZE = 100;
|
||||
const includes: Array<'media' | 'release-groups'> = ['media', 'release-groups'];
|
||||
@@ -303,187 +283,6 @@ const RELEASE_INCLUDES: Array<
|
||||
| 'release-groups'
|
||||
> = ['artist-credits', 'artists', 'media', 'recording-level-rels', 'recordings', 'release-groups'];
|
||||
|
||||
function normalizeArtistCreditToRelatedArtists(
|
||||
artistCredit: Array<{ artist: IArtist; name: string }>,
|
||||
): RelatedArtist[] {
|
||||
return artistCredit.map((ac) => ({
|
||||
id: `musicbrainz-${ac.artist.id}`,
|
||||
imageId: null,
|
||||
imageUrl: null,
|
||||
name: ac.name || ac.artist.name,
|
||||
userFavorite: false,
|
||||
userRating: null,
|
||||
}));
|
||||
}
|
||||
|
||||
function normalizeRecordingToSong(
|
||||
release: IRelease,
|
||||
medium: IMedium,
|
||||
track: ITrack,
|
||||
albumArtistName: string,
|
||||
albumArtists: RelatedArtist[],
|
||||
albumId: string,
|
||||
imageUrl: null | string,
|
||||
releaseDate: null | string,
|
||||
releaseYear: null | number,
|
||||
): Song {
|
||||
const recording = track.recording;
|
||||
const trackArtistCredit = track['artist-credit'] ?? recording['artist-credit'] ?? [];
|
||||
|
||||
const artistName =
|
||||
trackArtistCredit.map((ac) => ac.name).join('') || recording.title || track.title;
|
||||
|
||||
const artists = normalizeArtistCreditToRelatedArtists(
|
||||
trackArtistCredit as Array<{ artist: IArtist; name: string }>,
|
||||
);
|
||||
|
||||
const durationMilliseconds = track.length || recording.length || 0;
|
||||
const trackNumber = track.position || parseInt(track.number, 10) || 0;
|
||||
|
||||
return {
|
||||
_itemType: LibraryItem.SONG,
|
||||
_serverId: 'musicbrainz',
|
||||
_serverType: ServerType.EXTERNAL,
|
||||
album: release.title,
|
||||
albumArtistName,
|
||||
albumArtists,
|
||||
albumId,
|
||||
artistName,
|
||||
artists,
|
||||
bitDepth: null,
|
||||
bitRate: 0,
|
||||
bpm: null,
|
||||
channels: null,
|
||||
comment: null,
|
||||
compilation: null,
|
||||
container: null,
|
||||
createdAt: '',
|
||||
discNumber: medium.position || 1,
|
||||
discSubtitle: medium.title || null,
|
||||
duration: durationMilliseconds,
|
||||
explicitStatus: null,
|
||||
gain: null,
|
||||
genres: [],
|
||||
id: `musicbrainz-${release.id}-${recording.id}-${track.position}-${track.number}`,
|
||||
imageId: null,
|
||||
imageUrl,
|
||||
lastPlayedAt: null,
|
||||
lyrics: null,
|
||||
mbzRecordingId: recording.id,
|
||||
mbzTrackId: track.id,
|
||||
name: track.title || recording.title,
|
||||
participants: {},
|
||||
path: null,
|
||||
peak: null,
|
||||
playCount: 0,
|
||||
releaseDate,
|
||||
releaseYear,
|
||||
sampleRate: null,
|
||||
size: 0,
|
||||
sortName: track.title || recording.title,
|
||||
tags: null,
|
||||
trackNumber,
|
||||
trackSubtitle: null,
|
||||
updatedAt: '',
|
||||
userFavorite: false,
|
||||
userRating: null,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeReleaseToAlbum(release: IRelease): Album {
|
||||
const releaseGroup = release['release-group'];
|
||||
const artistCredit = release['artist-credit'] ?? releaseGroup?.['artist-credit'] ?? [];
|
||||
const albumArtistName = artistCredit.map((ac) => ac.name).join('') || release.title;
|
||||
const albumArtists: RelatedArtist[] = (artistCredit as { artist: IArtist; name: string }[]).map(
|
||||
(ac) => ({
|
||||
id: `musicbrainz-${ac.artist.id}`,
|
||||
imageId: null,
|
||||
imageUrl: null,
|
||||
name: ac.name || ac.artist.name,
|
||||
userFavorite: false,
|
||||
userRating: null,
|
||||
}),
|
||||
);
|
||||
|
||||
const hasArtwork =
|
||||
release['cover-art-archive']?.artwork === true &&
|
||||
release['cover-art-archive']?.front === true;
|
||||
const primaryReleaseType = releaseGroup?.['primary-type']?.toLowerCase() || null;
|
||||
const secondaryReleaseTypes =
|
||||
releaseGroup?.['secondary-types']?.map((type) => type.toLowerCase()) || [];
|
||||
const releaseTypes = [primaryReleaseType, ...secondaryReleaseTypes].filter(
|
||||
(type) => type !== null,
|
||||
) as string[];
|
||||
const isCompilation = releaseTypes.includes('compilation');
|
||||
const originalDate = releaseGroup?.['first-release-date'] || null;
|
||||
const originalYear = originalDate ? Number(originalDate.split('-')[0]) : null;
|
||||
const releaseDate = release.date ? release.date : null;
|
||||
const releaseYear = release.date ? Number(release.date.split('-')[0]) : null;
|
||||
const imageUrl = hasArtwork ? getImageUrl(release.id) : null;
|
||||
const albumId = `musicbrainz-${release.id}`;
|
||||
|
||||
const songs: Song[] = [];
|
||||
for (const medium of release.media ?? []) {
|
||||
for (const track of medium.tracks ?? []) {
|
||||
songs.push(
|
||||
normalizeRecordingToSong(
|
||||
release,
|
||||
medium,
|
||||
track,
|
||||
albumArtistName,
|
||||
albumArtists,
|
||||
albumId,
|
||||
imageUrl,
|
||||
releaseDate,
|
||||
releaseYear,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const totalDuration = songs.reduce((sum, s) => sum + s.duration, 0);
|
||||
|
||||
return {
|
||||
_itemType: LibraryItem.ALBUM,
|
||||
_serverId: 'musicbrainz',
|
||||
_serverType: ServerType.EXTERNAL,
|
||||
albumArtistName,
|
||||
albumArtists,
|
||||
artists: [],
|
||||
comment: null,
|
||||
createdAt: '',
|
||||
duration: totalDuration || null,
|
||||
explicitStatus: null,
|
||||
genres: [],
|
||||
id: albumId,
|
||||
imageId: null,
|
||||
imageUrl,
|
||||
isCompilation,
|
||||
lastPlayedAt: null,
|
||||
mbzId: release.id,
|
||||
mbzReleaseGroupId: releaseGroup?.id || null,
|
||||
name: release.title,
|
||||
originalDate,
|
||||
originalYear,
|
||||
participants: {},
|
||||
playCount: null,
|
||||
recordLabels: [],
|
||||
releaseDate,
|
||||
releaseType: primaryReleaseType,
|
||||
releaseTypes,
|
||||
releaseYear,
|
||||
size: null,
|
||||
songCount: songs.length,
|
||||
songs,
|
||||
sortName: release.title,
|
||||
tags: {},
|
||||
updatedAt: '',
|
||||
userFavorite: false,
|
||||
userRating: null,
|
||||
version: null,
|
||||
};
|
||||
}
|
||||
|
||||
export const musicbrainzQueries = {
|
||||
artist: (args: {
|
||||
excludeReleaseTypes?: string[];
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { queryOptions } from '@tanstack/react-query';
|
||||
|
||||
async function searchYoutube(query: string): Promise<Array<{ type: string; videoId?: string }>> {
|
||||
if (typeof window !== 'undefined' && window.api?.youtube) {
|
||||
return window.api.youtube.search(query);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export const youtubeQueries = {
|
||||
search: (args: { query: string }) => {
|
||||
return queryOptions({
|
||||
queryFn: () => searchYoutube(args.query),
|
||||
queryKey: ['youtube', 'search', args.query],
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,9 @@
|
||||
import { MUSICBRAINZ_ID_PREFIX } from '/@/renderer/features/musicbrainz/api/musicbrainz-api';
|
||||
import { IArtist, IRelease, IMedium, ITrack, IWork } from 'musicbrainz-api';
|
||||
import {
|
||||
IRelationWithWork,
|
||||
MUSICBRAINZ_ID_PREFIX,
|
||||
} from '/@/renderer/features/musicbrainz/api/musicbrainz-api';
|
||||
import { RelatedArtist, Song, LibraryItem, ServerType, Album } from '/@/shared/types/domain-types';
|
||||
|
||||
export function getImageUrl(releaseId: string): string {
|
||||
return `https://coverartarchive.org/release/${releaseId}/front-250.jpg`;
|
||||
@@ -293,3 +298,201 @@ const MBZ_RELEASE_TYPES = {
|
||||
soundtrack: 'soundtrack',
|
||||
spokenword: 'spokenword',
|
||||
};
|
||||
function normalizeArtistCreditToRelatedArtists(
|
||||
artistCredit: Array<{ artist: IArtist; name: string }>,
|
||||
): RelatedArtist[] {
|
||||
return artistCredit.map((ac) => ({
|
||||
id: `musicbrainz-${ac.artist.id}`,
|
||||
imageId: null,
|
||||
imageUrl: null,
|
||||
name: ac.name || ac.artist.name,
|
||||
userFavorite: false,
|
||||
userRating: null,
|
||||
}));
|
||||
}
|
||||
function normalizeRecordingToSong(
|
||||
release: IRelease,
|
||||
medium: IMedium,
|
||||
track: ITrack,
|
||||
albumArtistName: string,
|
||||
albumArtists: RelatedArtist[],
|
||||
albumId: string,
|
||||
imageUrl: null | string,
|
||||
releaseDate: null | string,
|
||||
releaseYear: null | number,
|
||||
): Song {
|
||||
const recording = track.recording;
|
||||
const trackArtistCredit = track['artist-credit'] ?? recording['artist-credit'] ?? [];
|
||||
|
||||
const artistName =
|
||||
trackArtistCredit.map((ac) => ac.name).join('') || recording.title || track.title;
|
||||
|
||||
const artists = normalizeArtistCreditToRelatedArtists(
|
||||
trackArtistCredit as Array<{ artist: IArtist; name: string }>,
|
||||
);
|
||||
|
||||
const durationMilliseconds = track.length || recording.length || 0;
|
||||
const trackNumber = track.position || parseInt(track.number, 10) || 0;
|
||||
|
||||
return {
|
||||
_itemType: LibraryItem.SONG,
|
||||
_serverId: 'musicbrainz',
|
||||
_serverType: ServerType.EXTERNAL,
|
||||
album: release.title,
|
||||
albumArtistName,
|
||||
albumArtists,
|
||||
albumId,
|
||||
artistName,
|
||||
artists,
|
||||
bitDepth: null,
|
||||
bitRate: 0,
|
||||
bpm: null,
|
||||
channels: null,
|
||||
comment: null,
|
||||
compilation: null,
|
||||
container: null,
|
||||
createdAt: '',
|
||||
discNumber: medium.position || 1,
|
||||
discSubtitle: medium.title || null,
|
||||
duration: durationMilliseconds,
|
||||
explicitStatus: null,
|
||||
gain: null,
|
||||
genres: [],
|
||||
id: `musicbrainz-${release.id}-${recording.id}-${track.position}-${track.number}`,
|
||||
imageId: null,
|
||||
imageUrl,
|
||||
lastPlayedAt: null,
|
||||
lyrics: null,
|
||||
mbzRecordingId: recording.id,
|
||||
mbzTrackId: track.id,
|
||||
name: track.title || recording.title,
|
||||
participants: {},
|
||||
path: null,
|
||||
peak: null,
|
||||
playCount: 0,
|
||||
releaseDate,
|
||||
releaseYear,
|
||||
sampleRate: null,
|
||||
size: 0,
|
||||
sortName: track.title || recording.title,
|
||||
tags: null,
|
||||
trackNumber,
|
||||
trackSubtitle: null,
|
||||
updatedAt: '',
|
||||
userFavorite: false,
|
||||
userRating: null,
|
||||
};
|
||||
}
|
||||
export function normalizeReleaseToAlbum(release: IRelease): Album {
|
||||
const releaseGroup = release['release-group'];
|
||||
const artistCredit = release['artist-credit'] ?? releaseGroup?.['artist-credit'] ?? [];
|
||||
const albumArtistName = artistCredit.map((ac) => ac.name).join('') || release.title;
|
||||
const albumArtists: RelatedArtist[] = (artistCredit as { artist: IArtist; name: string }[]).map(
|
||||
(ac) => ({
|
||||
id: `musicbrainz-${ac.artist.id}`,
|
||||
imageId: null,
|
||||
imageUrl: null,
|
||||
name: ac.name || ac.artist.name,
|
||||
userFavorite: false,
|
||||
userRating: null,
|
||||
}),
|
||||
);
|
||||
|
||||
const hasArtwork =
|
||||
release['cover-art-archive']?.artwork === true &&
|
||||
release['cover-art-archive']?.front === true;
|
||||
const primaryReleaseType = releaseGroup?.['primary-type']?.toLowerCase() || null;
|
||||
const secondaryReleaseTypes =
|
||||
releaseGroup?.['secondary-types']?.map((type) => type.toLowerCase()) || [];
|
||||
const releaseTypes = [primaryReleaseType, ...secondaryReleaseTypes].filter(
|
||||
(type) => type !== null,
|
||||
) as string[];
|
||||
const isCompilation = releaseTypes.includes('compilation');
|
||||
const originalDate = releaseGroup?.['first-release-date'] || null;
|
||||
const originalYear = originalDate ? Number(originalDate.split('-')[0]) : null;
|
||||
const releaseDate = release.date ? release.date : null;
|
||||
const releaseYear = release.date ? Number(release.date.split('-')[0]) : null;
|
||||
const imageUrl = hasArtwork ? getImageUrl(release.id) : null;
|
||||
const albumId = `musicbrainz-${release.id}`;
|
||||
|
||||
const songs: Song[] = [];
|
||||
for (const medium of release.media ?? []) {
|
||||
for (const track of medium.tracks ?? []) {
|
||||
songs.push(
|
||||
normalizeRecordingToSong(
|
||||
release,
|
||||
medium,
|
||||
track,
|
||||
albumArtistName,
|
||||
albumArtists,
|
||||
albumId,
|
||||
imageUrl,
|
||||
releaseDate,
|
||||
releaseYear,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const totalDuration = songs.reduce((sum, s) => sum + s.duration, 0);
|
||||
|
||||
return {
|
||||
_itemType: LibraryItem.ALBUM,
|
||||
_serverId: 'musicbrainz',
|
||||
_serverType: ServerType.EXTERNAL,
|
||||
albumArtistName,
|
||||
albumArtists,
|
||||
artists: [],
|
||||
comment: null,
|
||||
createdAt: '',
|
||||
duration: totalDuration || null,
|
||||
explicitStatus: null,
|
||||
genres: [],
|
||||
id: albumId,
|
||||
imageId: null,
|
||||
imageUrl,
|
||||
isCompilation,
|
||||
lastPlayedAt: null,
|
||||
mbzId: release.id,
|
||||
mbzReleaseGroupId: releaseGroup?.id || null,
|
||||
name: release.title,
|
||||
originalDate,
|
||||
originalYear,
|
||||
participants: {},
|
||||
playCount: null,
|
||||
recordLabels: [],
|
||||
releaseDate,
|
||||
releaseType: primaryReleaseType,
|
||||
releaseTypes,
|
||||
releaseYear,
|
||||
size: null,
|
||||
songCount: songs.length,
|
||||
songs,
|
||||
sortName: release.title,
|
||||
tags: {},
|
||||
updatedAt: '',
|
||||
userFavorite: false,
|
||||
userRating: null,
|
||||
version: null,
|
||||
};
|
||||
}
|
||||
export function collectWorksFromRelease(release: IRelease): IWork[] {
|
||||
const works: IWork[] = [];
|
||||
const seenIds = new Set<string>();
|
||||
|
||||
for (const medium of release.media ?? []) {
|
||||
for (const track of medium.tracks ?? []) {
|
||||
const recording = track.recording;
|
||||
const relations = (recording as { relations?: IRelationWithWork[] })?.relations ?? [];
|
||||
for (const rel of relations) {
|
||||
const work = (rel as IRelationWithWork).work;
|
||||
if (work?.id && !seenIds.has(work.id)) {
|
||||
seenIds.add(work.id);
|
||||
works.push(work);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return works;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMemo, useRef } from 'react';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { youtubeQueries } from '/@/renderer/features/musicbrainz/api/youtube-api';
|
||||
import { TranscodingConfig } from '/@/renderer/store';
|
||||
import { QueueSong } from '/@/shared/types/domain-types';
|
||||
import { QueueSong, ServerType } from '/@/shared/types/domain-types';
|
||||
|
||||
const YOUTUBE_WATCH_BASE = 'https://www.youtube.com/watch?v=';
|
||||
|
||||
export function useSongUrl(
|
||||
song: QueueSong | undefined,
|
||||
@@ -11,10 +15,36 @@ export function useSongUrl(
|
||||
): string | undefined {
|
||||
const prior = useRef(['', '']);
|
||||
|
||||
const isExternal = song?._serverType === ServerType.EXTERNAL;
|
||||
const searchQuery =
|
||||
song && isExternal ? `${song.artistName ?? ''} ${song.name ?? ''}`.trim() : '';
|
||||
|
||||
const youtubeSearch = useQuery({
|
||||
...youtubeQueries.search({ query: searchQuery }),
|
||||
enabled: Boolean(song && isExternal && searchQuery),
|
||||
});
|
||||
|
||||
const externalUrl = useMemo(() => {
|
||||
if (!song || !isExternal) return undefined;
|
||||
if (current && prior.current[0] === song._uniqueId && prior.current[1]) {
|
||||
return prior.current[1];
|
||||
}
|
||||
const url = getYoutubeUrlFromSearchResults(youtubeSearch.data);
|
||||
if (url) prior.current = [song._uniqueId, url];
|
||||
return url;
|
||||
}, [song, isExternal, current, youtubeSearch.data]);
|
||||
|
||||
return useMemo(() => {
|
||||
if (song?._serverId) {
|
||||
// If we are the current track, we do not want a transcoding
|
||||
// reconfiguration to force a restart.
|
||||
if (!song) {
|
||||
prior.current = ['', ''];
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (isExternal) {
|
||||
return externalUrl;
|
||||
}
|
||||
|
||||
if (song._serverId) {
|
||||
if (current && prior.current[0] === song._uniqueId) {
|
||||
return prior.current[1];
|
||||
}
|
||||
@@ -29,18 +59,16 @@ export function useSongUrl(
|
||||
},
|
||||
});
|
||||
|
||||
// transcoding enabled; save the updated result
|
||||
prior.current = [song._uniqueId, url];
|
||||
return url;
|
||||
}
|
||||
|
||||
// no track; clear result
|
||||
prior.current = ['', ''];
|
||||
return undefined;
|
||||
}, [
|
||||
song?._serverId,
|
||||
song?._uniqueId,
|
||||
song?.id,
|
||||
song,
|
||||
isExternal,
|
||||
externalUrl,
|
||||
current,
|
||||
transcode.bitrate,
|
||||
transcode.format,
|
||||
@@ -48,6 +76,16 @@ export function useSongUrl(
|
||||
]);
|
||||
}
|
||||
|
||||
function getYoutubeUrlFromSearchResults(
|
||||
results: Array<{ type: string; videoId?: string }> | undefined,
|
||||
): string | undefined {
|
||||
if (!results?.length) return undefined;
|
||||
const first = results.find((r) => r.type === 'SONG' || r.type === 'VIDEO');
|
||||
return first && 'videoId' in first && first.videoId
|
||||
? `${YOUTUBE_WATCH_BASE}${first.videoId}`
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export const getSongUrl = (song: QueueSong, transcode: TranscodingConfig) => {
|
||||
return api.controller.getStreamUrl({
|
||||
apiClientProps: { serverId: song._serverId },
|
||||
|
||||
@@ -7,7 +7,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
|
||||
import { updateQueueSong } from '/@/renderer/store/player.store';
|
||||
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||
import { QueueSong, SongDetailQuery } from '/@/shared/types/domain-types';
|
||||
import { QueueSong, ServerType, SongDetailQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
export const useUpdateCurrentSong = () => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -16,7 +16,11 @@ export const useUpdateCurrentSong = () => {
|
||||
async (properties: { index: number; song: QueueSong | undefined }) => {
|
||||
const currentSong = properties.song;
|
||||
|
||||
if (!currentSong?.id || !currentSong?._serverId) {
|
||||
if (
|
||||
!currentSong?.id ||
|
||||
!currentSong?._serverId ||
|
||||
currentSong?._serverType === ServerType.EXTERNAL
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user