Compare commits

...

160 Commits

Author SHA1 Message Date
c644d418dd fix(downloader): set player id to 0004e42
see https://github.com/LuanRT/YouTube.js/issues/1044#issuecomment-3332095783

Co-Authored-By: Shikki <56765352+theone2277@users.noreply.github.com>
2025-10-04 18:52:10 +03:00
18692b0283 chore(i18n): Translated using Weblate (Hungarian)
Currently translated at 95.8% (437 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/hu/
2025-10-04 14:02:01 +00:00
f81dd1f910 chore(i18n): Translated using Weblate (Romanian)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ro/
2025-10-03 09:02:01 +02:00
1452c4ae8a chore(i18n): Translated using Weblate (Tamil)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ta/
2025-10-02 04:02:08 +00:00
504c73df73 chore(i18n): Translated using Weblate (Dutch)
Currently translated at 99.3% (453 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nl/
2025-10-02 04:02:06 +00:00
0c16467a8b chore(i18n): Translated using Weblate (Dutch)
Currently translated at 99.3% (453 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nl/
2025-10-02 04:02:05 +00:00
26539544ab chore(i18n): Translated using Weblate (Dutch)
Currently translated at 99.3% (453 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nl/
2025-10-02 04:02:03 +00:00
9f84e2771c chore(i18n): Translated using Weblate (Russian)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2025-10-02 04:02:01 +00:00
a2be98588b chore(i18n): Translated using Weblate (Slovak)
Currently translated at 23.4% (107 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/sk/
2025-09-28 20:02:22 +00:00
b445ef8aeb chore(i18n): Translated using Weblate (Tamil)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ta/
2025-09-28 20:02:21 +00:00
a49ee4fd11 chore(i18n): Translated using Weblate (Malay)
Currently translated at 59.6% (272 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ms/
2025-09-28 20:02:20 +00:00
c8c380841f chore(i18n): Translated using Weblate (Hungarian)
Currently translated at 94.5% (431 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/hu/
2025-09-28 20:02:19 +00:00
6fa89db2f3 chore(i18n): Translated using Weblate (Hungarian)
Currently translated at 94.5% (431 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/hu/
2025-09-28 20:02:18 +00:00
c4ef6efddd chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2025-09-28 20:02:17 +00:00
55a6e815af chore(i18n): Translated using Weblate (Japanese)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ja/
2025-09-28 20:02:16 +00:00
8238262afe Clarify application description in README 2025-09-28 22:07:05 +09:00
a81aa4b7c9 Update README with disclaimer and liability section
Added disclaimers regarding affiliation and liability.
2025-09-28 22:06:23 +09:00
56c903d5df add disclaimer 2025-09-28 22:04:28 +09:00
b0931c8924 chore(i18n): Translated using Weblate (Hindi)
Currently translated at 85.0% (388 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/hi/
2025-09-27 18:02:02 +00:00
5142b673c1 chore(i18n): Translated using Weblate (Slovak)
Currently translated at 18.8% (86 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/sk/
2025-09-25 20:02:06 +00:00
36eaeef45f chore(i18n): Translated using Weblate (Malay)
Currently translated at 48.9% (223 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ms/
2025-09-25 20:02:04 +00:00
da2f3e5102 chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 99.7% (455 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2025-09-25 20:02:03 +00:00
7ceb160e96 chore(i18n): Translated using Weblate (Czech)
Currently translated at 99.7% (455 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2025-09-25 20:02:00 +00:00
a563084055 chore(i18n): Added translation using Weblate (Slovak) 2025-09-24 19:56:33 +00:00
ced531133a chore(i18n): Translated using Weblate (Czech)
Currently translated at 99.3% (453 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2025-09-24 19:56:32 +00:00
345c2e59fd remove assets from docs 2025-09-24 22:01:07 +09:00
b3e0fd5588 chore(i18n): Translated using Weblate (Malay)
Currently translated at 39.4% (180 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ms/
2025-09-24 11:02:02 +00:00
7f35738667 chore(i18n): Translated using Weblate (Polish)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pl/
2025-09-24 11:02:01 +00:00
1fa827df89 chore(i18n): Translated using Weblate (Catalan)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ca/
2025-09-23 00:02:02 +02:00
78ba710b3f chore(i18n): Translated using Weblate (Hungarian)
Currently translated at 91.0% (415 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/hu/
2025-09-23 00:02:00 +02:00
bcb61a922e chore(i18n): Translated using Weblate (Arabic)
Currently translated at 95.8% (437 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ar/
2025-09-21 10:02:03 +02:00
c046a76972 chore(i18n): Translated using Weblate (Arabic)
Currently translated at 95.8% (437 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ar/
2025-09-21 10:02:02 +02:00
ed25d11b23 chore(i18n): Translated using Weblate (French)
Currently translated at 99.7% (455 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2025-09-20 04:02:00 +02:00
8de5599240 chore(i18n): Translated using Weblate (Danish)
Currently translated at 32.0% (146 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/da/
2025-09-19 00:02:01 +02:00
ce7fcc5d01 chore(i18n): Translated using Weblate (Bulgarian)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/bg/
2025-09-19 00:02:00 +02:00
30ed2b5c75 remove bypass-age-restrictions 2025-09-18 07:33:54 +09:00
12d4241668 chore(docs): update README 2025-09-18 07:31:26 +09:00
0eb65f082c chore(docs): update README 2025-09-18 07:29:59 +09:00
25fccc9a62 remove assets 2025-09-18 07:25:23 +09:00
414a560205 Delete assets/youtube-music.svg 2025-09-18 07:23:24 +09:00
7c1b8ed0a4 Delete assets/youtube-music.png 2025-09-18 07:23:12 +09:00
8084a175cf REMOVE image 2025-09-17 17:56:36 +09:00
f5175a6be7 REMOVE adblocker AND no-google-login, and renaming 2025-09-17 17:48:12 +09:00
c9ae7cb277 chore(i18n): Translated using Weblate (Italian)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2025-09-15 14:01:59 +00:00
e455932754 chore(i18n): Translated using Weblate (Catalan)
Currently translated at 98.2% (448 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ca/
2025-09-14 12:02:01 +02:00
a1e3bf23be chore(i18n): Translated using Weblate (Catalan)
Currently translated at 98.2% (448 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ca/
2025-09-14 12:02:00 +02:00
8ccd510700 chore(i18n): Translated using Weblate (Italian)
Currently translated at 98.9% (451 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2025-09-14 12:01:59 +02:00
Bai
c0a495640f chore(i18n): Translated using Weblate (Turkish)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2025-09-13 07:52:22 +02:00
6060e138ee chore(i18n): Translated using Weblate (German)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2025-09-13 07:52:22 +02:00
98b2e182fb chore(i18n): Translated using Weblate (German)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/de/
2025-09-13 07:52:22 +02:00
715ddb8923 chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2025-09-12 12:01:59 +02:00
3104111f6b chore(i18n): Translated using Weblate (Thai)
Currently translated at 99.7% (455 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/th/
2025-09-11 09:02:10 +00:00
e064c37859 chore(i18n): Translated using Weblate (Russian)
Currently translated at 99.7% (455 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2025-09-11 09:02:08 +00:00
1de223b4ca chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2025-09-11 09:02:06 +00:00
50a1365a6f chore(i18n): Translated using Weblate (Czech)
Currently translated at 91.4% (417 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/cs/
2025-09-11 09:02:04 +00:00
cc83804491 chore(i18n): Translated using Weblate (Filipino)
Currently translated at 83.9% (383 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fil/
2025-09-09 19:02:05 +00:00
92cf306439 chore(i18n): Translated using Weblate (Swedish)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/sv/
2025-09-09 19:02:04 +00:00
a8be510a07 chore(i18n): Translated using Weblate (Vietnamese)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/vi/
2025-09-09 19:02:03 +00:00
58524fb3c1 chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 96.7% (441 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2025-09-09 19:02:02 +00:00
aeb6997117 chore(i18n): Translated using Weblate (Portuguese)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt/
2025-09-09 19:02:01 +00:00
0116188623 fix: maybe fix #3613 2025-09-09 23:43:42 +09:00
af63edb058 fix: update pnpm-lock.yml 2025-09-09 22:42:42 +09:00
bd0ac52832 Revert "fix(deps): update dependency color to v5.0.1 (#3846)" (#3855) 2025-09-09 22:35:32 +09:00
5b537d2a9e fix(deps): update dependency virtua to v0.42.3 (#3847)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-09 22:08:10 +09:00
d9a7c352d3 chore(deps): update dependency typescript-eslint to v8.43.0 (#3848)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-09 22:07:58 +09:00
84add37441 fix(deps): update dependency color to v5.0.1 (#3846)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-09 08:34:11 +09:00
9a88f11f16 chore(deps): update dependency vite to v7.1.8 (#3844)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-09 08:33:47 +09:00
51b4441e5c chore(deps): update dependency ts-morph to v27 (#3841)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-09 08:31:35 +09:00
29784e6a39 chore(i18n): Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hant/
2025-09-08 18:19:17 +02:00
e2981c22fa chore(i18n): Translated using Weblate (Malayalam)
Currently translated at 11.6% (53 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ml/
2025-09-08 14:02:08 +00:00
d7513dcc20 chore(i18n): Translated using Weblate (Serbian)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/sr/
2025-09-08 14:02:07 +00:00
5c0face2c9 chore(i18n): Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt_BR/
2025-09-08 14:02:04 +00:00
a1805f7662 chore(i18n): Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt_BR/
2025-09-08 14:02:03 +00:00
46889098e8 chore(i18n): Translated using Weblate (Dutch)
Currently translated at 95.1% (434 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/nl/
2025-09-08 14:02:02 +00:00
607938c170 chore(i18n): Translated using Weblate (Indonesian)
Currently translated at 96.2% (439 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/id/
2025-09-08 14:02:00 +00:00
58a03db898 chore(i18n): Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2025-09-08 14:01:59 +00:00
67ef3408dc chore(i18n): Translated using Weblate (Italian)
Currently translated at 98.0% (447 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/it/
2025-09-08 14:01:57 +00:00
27f16e26b3 chore(i18n): Translated using Weblate (French)
Currently translated at 99.7% (455 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/fr/
2025-09-08 14:01:56 +00:00
e38200bc1f chore(i18n): Translated using Weblate (Spanish)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/es/
2025-09-08 14:01:54 +00:00
736e63d259 Update changelog for v3.11.0 2025-09-07 09:01:30 +00:00
721271d902 fix: Update changelog script in package.json 2025-09-07 15:38:35 +09:00
a2151930ec Bump version to 3.11.0 2025-09-07 15:18:33 +09:00
bdc9f42681 fix: change deprecated option rollupOptions to rolldownOptions 2025-09-07 15:18:02 +09:00
0b3c6f9e1f chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (456 of 456 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2025-09-07 08:16:14 +02:00
ebe373bdc6 chore(package.json): add NODE_ENV 2025-09-07 13:45:40 +09:00
ab91e6d735 Fixed missing videochange dataupdated event when using shuffle (#3659)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: rewhex <gitea@cluser.local>
2025-09-07 13:43:48 +09:00
be3ae4d789 feat(synced-lyrics): preferred provider (global/per-song) (#3741)
Co-authored-by: JellyBrick <shlee1503@naver.com>
2025-09-07 13:35:29 +09:00
336b7fe5e9 fix(api-server): use ipc instead of ipcMain 2025-09-07 12:47:08 +09:00
5ecd39f324 feat(api-server): send shuffle state over websocket (#3837) 2025-09-07 04:29:38 +09:00
eb50596961 feat(synced-lyrics): add new "spacer" (#3742)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-07 04:26:03 +09:00
aede0cd643 chore(i18n): Translated using Weblate (Vietnamese)
Currently translated at 100.0% (452 of 452 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/vi/
2025-09-06 17:20:17 +02:00
25f51784c3 chore(i18n): Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (452 of 452 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/zh_Hans/
2025-09-06 17:20:17 +02:00
7acac6d11c chore(i18n): Translated using Weblate (Turkish)
Currently translated at 100.0% (452 of 452 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/tr/
2025-09-06 17:20:16 +02:00
1972ef70b5 chore(i18n): Translated using Weblate (Russian)
Currently translated at 99.7% (451 of 452 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2025-09-06 17:20:16 +02:00
c4455ed3d2 chore(i18n): Translated using Weblate (Korean)
Currently translated at 100.0% (452 of 452 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ko/
2025-09-06 17:20:16 +02:00
c1a06ab955 feat: remove unnecessary export default for better tree-shaking 2025-09-06 22:31:02 +09:00
26fa1f85b2 fix(discord-rpc, scrobbler): Align artist and title with the last.fm's de facto standard
- Display only the main artist.
- Display the title in its original language without romanization.

- fix #3358
- fix #3641
2025-09-06 10:25:54 +09:00
555817e2f5 feat(downloader): Add context menu button for playlists and albums (#3768)
Co-authored-by: JellyBrick <shlee1503@naver.com>
2025-09-06 09:12:24 +09:00
f8654dfdb9 fix(i18n): fix missing i18n 2025-09-06 09:08:17 +09:00
96c0fc412c chore(i18n): Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (442 of 442 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/pt_BR/
2025-09-06 00:00:58 +00:00
a70a4106df chore(i18n): Translated using Weblate (Russian)
Currently translated at 100.0% (442 of 442 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/ru/
2025-09-06 00:00:57 +00:00
895210cbb6 feat(transparent-player): new plugin for Acrylic, Mica or Tabbed effects (#3529)
Co-authored-by: JellyBrick <shlee1503@naver.com>
2025-09-06 09:00:39 +09:00
789a30312b fix(reviewdog): checkout PR HEAD 2025-09-06 06:38:36 +09:00
623d97b1e2 fix(reviewdog): run eslint for changed files 2025-09-06 06:28:52 +09:00
4ed97f0145 fix: action should catch errors 2025-09-06 06:22:19 +09:00
77a2bbf02a fix(action): fix permission 2025-09-06 06:09:03 +09:00
7a9a1531d4 fix(action): fix auth 2025-09-06 05:35:59 +09:00
16b59698d6 fix(action): fix node version 2025-09-06 05:30:58 +09:00
a85a2e0c58 fix(action): fix install deps 2025-09-06 05:27:38 +09:00
97f1a20a4f fix(actions): fix mistake 2025-09-06 05:19:21 +09:00
8dbe151ddd fix(audio-compressor): real-time behavior and duplicated audio bug (#3786)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-06 05:15:47 +09:00
87144e03c2 fix(actions): fix node / pnpm version 2025-09-06 05:13:51 +09:00
2046b253e3 fix: Added Min height and width to the window which doesnt breaks the UI responsiveness (#3602) 2025-09-06 05:04:16 +09:00
c068e11fc5 feat(actions): add reviewdog 2025-09-06 04:54:53 +09:00
10384b6c4c chore(eslint): added new eslint rule (for jsx) 2025-09-06 04:51:31 +09:00
4d83bd587d chore(deps): update dependency discord-api-types to v0.38.23 (#3833)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 02:55:56 +09:00
aae523989b feat(plugin): Custom output device plugin (#3789)
Co-authored-by: Angelos Bouklis <me@arjix.dev>
Co-authored-by: JellyBrick <shlee1503@naver.com>
2025-09-06 02:54:07 +09:00
afacec973b chore(deps): update dependency @babel/runtime to v7.28.4 (#3831)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 01:20:54 +09:00
75fb51e290 chore(deps): update eslint monorepo to v9.35.0 (#3829)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 01:18:02 +09:00
cb85048af4 feat(api-server): Add websocket as /api/v1/ws route (#3707)
Co-authored-by: JellyBrick <shlee1503@naver.com>
2025-09-06 01:17:32 +09:00
8b10872e83 feat(api-server): Improved api-server volume and like/dislike state (#3592)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: rewhex <gitea@cluser.local>
Co-authored-by: JellyBrick <shlee1503@naver.com>
2025-09-05 23:59:39 +09:00
96ea114335 fix: pnpm-lock.yaml 2025-09-05 23:02:15 +09:00
7c1c3ef28d Merge remote-tracking branch 'origin/master' 2025-09-05 22:45:43 +09:00
7a7ad4261c fix(deps): update dependency i18next to v25.5.2 (#3826)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 22:45:33 +09:00
c0dbc204a0 fix(deps): update dependency virtua to v0.42.2 (#3827)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 22:45:19 +09:00
68e63f809c fix: apply fix from eslint 2025-09-05 22:43:34 +09:00
4b188ec205 chore(i18n): Translated using Weblate (Thai)
Currently translated at 100.0% (437 of 437 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/th/
2025-09-05 15:07:03 +02:00
23013cddb9 fix(exponential-volume): volume desync bug (#3787)
Co-authored-by: JellyBrick <shlee1503@naver.com>
2025-09-05 20:53:07 +09:00
588b84ecd0 feat(synced-lyrics): thai romanization (#3618)
Co-authored-by: Angelos Bouklis <me@arjix.dev>
Co-authored-by: JellyBrick <shlee1503@naver.com>
2025-09-05 16:30:39 +09:00
fd68c204f6 chore(i18n): Translated using Weblate (Swedish)
Currently translated at 100.0% (433 of 433 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/sv/
2025-09-05 06:58:38 +00:00
313bb6e43f chore(i18n): Translated using Weblate (Swedish)
Currently translated at 100.0% (433 of 433 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/sv/
2025-09-05 06:58:37 +00:00
0a6f244035 chore(i18n): Translated using Weblate (Swedish)
Currently translated at 100.0% (433 of 433 strings)

Translation: th-ch/youtube-music/i18n
Translate-URL: https://hosted.weblate.org/projects/youtube-music/i18n/sv/
2025-09-05 06:58:36 +00:00
8e4e2c42f6 feat(discord): add option to display artist/title in status (#3692)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: JellyBrick <shlee1503@naver.com>
2025-09-05 15:58:26 +09:00
f31053cf3c chore(deps): update playwright monorepo to v1.55.0 (#3819)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:43:02 +09:00
d5758790c0 fix(deps): update dependency i18next to v25.5.1 (#3820)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:42:52 +09:00
bbd243a534 fix(deps): update dependency virtua to v0.42.0 (#3821)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:42:41 +09:00
2fc0d6f3b0 fix(deps): update dependency zod to v4.1.5 (#3822)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:42:31 +09:00
6b15018a9b chore(deps): update eslint monorepo to v9.34.0 (#3818)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:42:24 +09:00
acc977db7c chore(deps): update actions/setup-node action to v5 (#3823)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:42:03 +09:00
270100a14c chore(deps): update dependency electron to v38 (#3824)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:41:43 +09:00
094e6fa2d6 chore(deps): update dependency @stylistic/eslint-plugin to v5.3.1 (#3817)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:40:46 +09:00
9f81f7001c fix(deps): update dependency serve to v14.2.5 (#3816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:40:21 +09:00
lew
e6c78dd5e0 feat(discord): add song & artist URLs to rich presence (#3737)
Co-authored-by: JellyBrick <shlee1503@naver.com>
2025-09-05 15:32:31 +09:00
b64e1394ae fix: fix #3621 (#3774) 2025-09-05 15:32:15 +09:00
dcc611c7d0 feat(refactor): PluginDefinition::platform (#3665) 2025-09-05 15:28:17 +09:00
d329076b52 chore(docs): update copyright footer year (#3792) 2025-09-05 15:10:28 +09:00
1435559a56 chore(deps): update dependency vite-plugin-inspect to v11.3.3 (#3814)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:08:36 +09:00
1feeeedf10 fix(deps): update dependency @floating-ui/dom to v1.7.4 (#3815)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:07:50 +09:00
443d716e45 fix(deps): update dependency @ghostery/adblocker-electron to v2.11.6 (#3770)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:01:46 +09:00
3d65a96e38 chore(deps): update dependency discord-api-types to v0.38.22 (#3813)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:01:37 +09:00
e20f3fe24c chore(deps): update dependency @types/semver to v7.7.1 (#3812)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 15:01:30 +09:00
5d3afb52d8 fix(deps): update dependency @ghostery/adblocker-electron-preload to v2.11.6 (#3771)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 14:59:44 +09:00
5e0341c8d5 fix(deps): update dependency @hono/node-server to v1.19.1 (#3759)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 14:58:08 +09:00
b84a8c512a chore(deps): update dependency typescript-eslint to v8.42.0 (#3761)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 14:57:52 +09:00
cd0f4bbc1d chore(deps): update dependency node-gyp to v11.4.2 (#3772)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 14:57:38 +09:00
e37367c5e5 chore(deps): update actions/checkout action to v5 (#3757)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 14:55:52 +09:00
8a765be912 chore(deps): update dependency vite to v7.1.5 (#3760)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 14:54:47 +09:00
a213dae14d fix(deps): update dependency hono to v4.9.6 [security] (#3807)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 14:50:33 +09:00
40c429f3c1 chore(deps): update dependency electron to v37.3.1 [security] (#3806)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 14:50:13 +09:00
3125520e68 chore(deps): bump hono from 4.9.2 to 4.9.6 (#3805)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 14:49:48 +09:00
191 changed files with 5186 additions and 2168 deletions

View File

@ -18,7 +18,7 @@ jobs:
os: [ macos-latest, ubuntu-latest, windows-latest ]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install pnpm
uses: pnpm/action-setup@v4
@ -28,14 +28,14 @@ jobs:
- name: Setup NodeJS
if: startsWith(matrix.os, 'macOS') != true
uses: actions/setup-node@v4
uses: actions/setup-node@v5
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Setup NodeJS for macOS
if: startsWith(matrix.os, 'macOS')
uses: actions/setup-node@v4
uses: actions/setup-node@v5
with:
node-version: ${{ env.NODE_VERSION }}
@ -91,7 +91,7 @@ jobs:
if: github.repository == 'th-ch/youtube-music' && github.ref == 'refs/heads/master'
needs: build
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
@ -103,14 +103,14 @@ jobs:
- name: Setup NodeJS
if: startsWith(matrix.os, 'macOS') != true
uses: actions/setup-node@v4
uses: actions/setup-node@v5
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Setup NodeJS for macOS
if: startsWith(matrix.os, 'macOS')
uses: actions/setup-node@v4
uses: actions/setup-node@v5
with:
node-version: ${{ env.NODE_VERSION }}

View File

@ -15,6 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: "Checkout Repository"
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: "Dependency Review"
uses: actions/dependency-review-action@v4

41
.github/workflows/reviewdog.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: reviewdog
on: [pull_request_target]
env:
NODE_VERSION: "22.x"
jobs:
eslint:
name: runner / eslint
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
checks: write
steps:
- uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
run_install: false
- name: Setup NodeJS
uses: actions/setup-node@v5
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- uses: reviewdog/action-eslint@v1.34.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review # Change reporter.
eslint_flags: './src'
fail_level: error

149
README.md
View File

@ -1,6 +1,6 @@
<div align="center">
# YouTube Music
# YTMD
[![GitHub release](https://img.shields.io/github/release/th-ch/youtube-music.svg?style=for-the-badge&logo=youtube-music)](https://github.com/th-ch/youtube-music/releases/)
[![GitHub license](https://img.shields.io/github/license/th-ch/youtube-music.svg?style=for-the-badge)](https://github.com/th-ch/youtube-music/blob/master/license)
@ -14,31 +14,27 @@
![Screenshot](web/screenshot.png "Screenshot")
<div align="center">
<a href="https://github.com/th-ch/youtube-music/releases/latest">
<img src="web/youtube-music.svg" width="400" height="100" alt="YouTube Music SVG">
</a>
</div>
Read this in other languages: [한국어](./docs/readme/README-ko.md), [Française](./docs/readme/README-fr.md), [Íslenska](./docs/readme/README-is.md), [Español](./docs/readme/README-es.md), [Pусский](./docs/readme/README-ru.md), [Українська](./docs/readme/README-uk.md), [Magyar](./docs/readme/README-hu.md), [Português](./docs/readme/README-pt.md), [日本語](./docs/readme/README-ja.md)
**Electron wrapper around YouTube Music featuring:**
- Native look & feel, aims at keeping the original interface
- Framework for custom plugins: change YouTube Music to your needs (style, content, features), enable/disable plugins in
- Native look & feel extension, aims at keeping the original interface
one click
## Demo Image
| Player Screen (album color theme & ambient light) |
|:---------------------------------------------------------------------------------------------------------:|
|![Screenshot1](https://github.com/th-ch/youtube-music/assets/16558115/53efdf73-b8fa-4d7b-a235-b96b91ea77fc)|
> [!IMPORTANT]
> ⚠️ Disclaimer
>
> **No Affiliation**
>
> This project, and its contributors, are not affiliated with, authorized by, endorsed by, or in any way officially connected with Google LLC, YouTube, or any of their subsidiaries or affiliates. **This is an independent, non-profit, and unofficial extension developed by a team of volunteers with the goal of providing a desktop experience.**
>
> **Trademarks**
>
> The names "Google" and "YouTube Music", as well as related names, marks, emblems, and images, are registered trademarks of their respective owners. Any use of these trademarks is for identification and reference purposes only and does not imply any association with the trademark holder. We have no intention of infringing upon these trademarks or causing harm to the trademark holders.
>
> **Limitation of Liability**
>
> This application (extension) is provided "AS IS", and you use it at your own risk. In no event shall the developers or contributors be liable for any claim, damages, or other liability, including any legal consequences, arising from, out of, or in connection with the software or the use or other dealings in the software. The responsibility for any and all outcomes of using this software rests entirely with the user.
## Content
- [Features](#features)
- [Available plugins](#available-plugins)
- [Translation](#translation)
- [Download](#download)
- [Arch Linux](#arch-linux)
@ -56,116 +52,6 @@ Read this in other languages: [한국어](./docs/readme/README-ko.md), [Françai
- [License](#license)
- [FAQ](#faq)
## Features:
- **Auto confirm when paused** (Always Enabled): disable
the ["Continue Watching?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png)
popup that pause music after a certain time
- And more ...
## Available plugins:
- **Ad Blocker**: Block all ads and tracking out of the box
- **Album Actions**: Adds Undislike, Dislike, Like, and Unlike buttons to apply this to all songs in a playlist or album
- **Album Color Theme**: Applies a dynamic theme and visual effects based on the album color palette
- **Ambient Mode**: Applies a lighting effect by casting gentle colors from the video, into your screens background
- **Audio Compressor**: Apply compression to audio (lowers the volume of the loudest parts of the signal and raises the
volume of the softest parts)
- **Blur Navigation Bar**: makes navigation bar transparent and blurry
- **Bypass Age Restrictions**: bypass YouTube's age verification
- **Captions Selector**: Enable captions
- **Compact Sidebar**: Always set the sidebar in compact mode
- **Crossfade**: Crossfade between songs
- **Disable Autoplay**: Makes every song start in "paused" mode
- **[Discord](https://discord.com/) Rich Presence**: Show your friends what you listen to
with [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
- **Downloader**: downloads
MP3 [directly from the interface](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
- **Equalizer**: add filters to boost or cut specific range of frequencies (e.g. bass booster)
- **Exponential Volume**: Makes the volume
slider [exponential](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) so it's easier to
select lower volumes
- **In-App Menu**: [gives bars a fancy, dark look](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
> (see [this post](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) if you have problem
accessing the menu after enabling this plugin and hide-menu option)
- **Scrobbler**: Adds scrobbling support for [Last.fm](https://www.last.fm/) and [ListenBrainz](https://listenbrainz.org/)
- **Lumia Stream**: Adds [Lumia Stream](https://lumiastream.com/) support
- **Lyrics Genius**: Adds lyrics support for most songs
- **Music Together**: Share a playlist with others. When the host plays a song, everyone else will hear the same song
- **Navigation**: Next/Back navigation arrows directly integrated in the interface, like in your favorite browser
- **No Google Login**: Remove Google login buttons and links from the interface
- **Notifications**: Display a notification when a song starts
playing ([interactive notifications](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png)
are available on windows)
- **Picture-in-picture**: allows to switch the app to picture-in-picture mode
- **Playback Speed**: Listen fast, listen
slow! [Adds a slider that controls song speed](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
- **Precise Volume**: Control the volume precisely using mousewheel/hotkeys, with a custom hud and customizable volume
steps
- **Shortcuts (& MPRIS)**: Allows setting global hotkeys for playback (play/pause/next/previous) +
disable [media osd](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png)
by overriding media keys + enable Ctrl/CMD + F to search + enable linux mpris support for
mediakeys + [custom hotkeys](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50)
for [advanced users](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
- **Skip Disliked Song**: Skips disliked songs
- **Skip Silences**: Automatically skip silenced sections
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): Automatically Skips non-music parts like intro/outro or
parts of music videos where the song isn't playing
- **Synced Lyrics**: Provides synced lyrics to songs, using providers like [LRClib](https://lrclib.net).
- **Taskbar Media Control**: Control playback from
your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
- **TouchBar**: Custom TouchBar layout for macOS
- **Tuna OBS**: Integration with [OBS](https://obsproject.com/)'s
plugin [Tuna](https://obsproject.com/forum/resources/tuna.843/)
- **Unobtrusive Player**: Prevents the player from popping up when playing a song
- **Video Quality Changer**: Allows changing the video quality with
a [button](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) on
the video overlay
- **Video Toggle**: Adds
a [button](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) to
switch between Video/Song mode. can also optionally remove the whole video tab
- **Visualizer**: Different music visualizers
## Translation
You can help with translation on [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/).
@ -355,8 +241,7 @@ export default createPlugin({
enabled: false,
}, // your custom config
renderer() {
// Remove the login button
document.querySelector(".sign-in-link.ytmusic-nav-bar").remove();
console.log('hello from renderer');
} // define renderer hook
});
```

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 931 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View File

@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 176 176" width="32" height="32">
<circle fill="red" cx="88" cy="88" r="88"/>
<path fill="#FFF"
d="M88 46c23.1 0 42 18.8 42 42s-18.8 42-42 42-42-18.8-42-42 18.9-42 42-42m0-4c-25.4 0-46 20.6-46 46s20.6 46 46 46 46-20.6 46-46-20.6-46-46-46z"/>
<path fill="#FFF" d="M72 111l39-24-39-22z"/>
</svg>

Before

Width:  |  Height:  |  Size: 353 B

View File

@ -2,8 +2,96 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [v3.11.0](https://github.com/th-ch/youtube-music/compare/v3.10.0...v3.11.0)
- Fixed missing videochange dataupdated event when using shuffle [`#3659`](https://github.com/th-ch/youtube-music/pull/3659)
- feat(synced-lyrics): preferred provider (global/per-song) [`#3741`](https://github.com/th-ch/youtube-music/pull/3741)
- feat(api-server): send shuffle state over websocket [`#3837`](https://github.com/th-ch/youtube-music/pull/3837)
- feat(synced-lyrics): add new "spacer" [`#3742`](https://github.com/th-ch/youtube-music/pull/3742)
- feat(downloader): Add context menu button for playlists and albums [`#3768`](https://github.com/th-ch/youtube-music/pull/3768)
- feat(transparent-player): new plugin for Acrylic, Mica or Tabbed effects [`#3529`](https://github.com/th-ch/youtube-music/pull/3529)
- fix(audio-compressor): real-time behavior and duplicated audio bug [`#3786`](https://github.com/th-ch/youtube-music/pull/3786)
- fix: Added Min height and width to the window which doesnt breaks the UI responsiveness [`#3602`](https://github.com/th-ch/youtube-music/pull/3602)
- chore(deps): update dependency discord-api-types to v0.38.23 [`#3833`](https://github.com/th-ch/youtube-music/pull/3833)
- feat(plugin): Custom output device plugin [`#3789`](https://github.com/th-ch/youtube-music/pull/3789)
- chore(deps): update dependency @babel/runtime to v7.28.4 [`#3831`](https://github.com/th-ch/youtube-music/pull/3831)
- chore(deps): update eslint monorepo to v9.35.0 [`#3829`](https://github.com/th-ch/youtube-music/pull/3829)
- feat(api-server): Add websocket as `/api/v1/ws` route [`#3707`](https://github.com/th-ch/youtube-music/pull/3707)
- feat(api-server): Improved api-server volume and like/dislike state [`#3592`](https://github.com/th-ch/youtube-music/pull/3592)
- fix(deps): update dependency i18next to v25.5.2 [`#3826`](https://github.com/th-ch/youtube-music/pull/3826)
- fix(deps): update dependency virtua to v0.42.2 [`#3827`](https://github.com/th-ch/youtube-music/pull/3827)
- fix(exponential-volume): volume desync bug [`#3787`](https://github.com/th-ch/youtube-music/pull/3787)
- feat(synced-lyrics): thai romanization [`#3618`](https://github.com/th-ch/youtube-music/pull/3618)
- feat(discord): add option to display artist/title in status [`#3692`](https://github.com/th-ch/youtube-music/pull/3692)
- chore(deps): update playwright monorepo to v1.55.0 [`#3819`](https://github.com/th-ch/youtube-music/pull/3819)
- fix(deps): update dependency i18next to v25.5.1 [`#3820`](https://github.com/th-ch/youtube-music/pull/3820)
- fix(deps): update dependency virtua to v0.42.0 [`#3821`](https://github.com/th-ch/youtube-music/pull/3821)
- fix(deps): update dependency zod to v4.1.5 [`#3822`](https://github.com/th-ch/youtube-music/pull/3822)
- chore(deps): update eslint monorepo to v9.34.0 [`#3818`](https://github.com/th-ch/youtube-music/pull/3818)
- chore(deps): update actions/setup-node action to v5 [`#3823`](https://github.com/th-ch/youtube-music/pull/3823)
- chore(deps): update dependency electron to v38 [`#3824`](https://github.com/th-ch/youtube-music/pull/3824)
- chore(deps): update dependency @stylistic/eslint-plugin to v5.3.1 [`#3817`](https://github.com/th-ch/youtube-music/pull/3817)
- fix(deps): update dependency serve to v14.2.5 [`#3816`](https://github.com/th-ch/youtube-music/pull/3816)
- feat(discord): add song & artist URLs to rich presence [`#3737`](https://github.com/th-ch/youtube-music/pull/3737)
- fix: fix #3621 [`#3774`](https://github.com/th-ch/youtube-music/pull/3774)
- feat(refactor): PluginDefinition::platform [`#3665`](https://github.com/th-ch/youtube-music/pull/3665)
- chore(docs): update copyright footer year [`#3792`](https://github.com/th-ch/youtube-music/pull/3792)
- chore(deps): update dependency vite-plugin-inspect to v11.3.3 [`#3814`](https://github.com/th-ch/youtube-music/pull/3814)
- fix(deps): update dependency @floating-ui/dom to v1.7.4 [`#3815`](https://github.com/th-ch/youtube-music/pull/3815)
- fix(deps): update dependency @ghostery/adblocker-electron to v2.11.6 [`#3770`](https://github.com/th-ch/youtube-music/pull/3770)
- chore(deps): update dependency discord-api-types to v0.38.22 [`#3813`](https://github.com/th-ch/youtube-music/pull/3813)
- chore(deps): update dependency @types/semver to v7.7.1 [`#3812`](https://github.com/th-ch/youtube-music/pull/3812)
- fix(deps): update dependency @ghostery/adblocker-electron-preload to v2.11.6 [`#3771`](https://github.com/th-ch/youtube-music/pull/3771)
- fix(deps): update dependency @hono/node-server to v1.19.1 [`#3759`](https://github.com/th-ch/youtube-music/pull/3759)
- chore(deps): update dependency typescript-eslint to v8.42.0 [`#3761`](https://github.com/th-ch/youtube-music/pull/3761)
- chore(deps): update dependency node-gyp to v11.4.2 [`#3772`](https://github.com/th-ch/youtube-music/pull/3772)
- chore(deps): update actions/checkout action to v5 [`#3757`](https://github.com/th-ch/youtube-music/pull/3757)
- chore(deps): update dependency vite to v7.1.5 [`#3760`](https://github.com/th-ch/youtube-music/pull/3760)
- fix(deps): update dependency hono to v4.9.6 [security] [`#3807`](https://github.com/th-ch/youtube-music/pull/3807)
- chore(deps): update dependency electron to v37.3.1 [security] [`#3806`](https://github.com/th-ch/youtube-music/pull/3806)
- chore(deps): bump hono from 4.9.2 to 4.9.6 [`#3805`](https://github.com/th-ch/youtube-music/pull/3805)
- chore(deps): update playwright monorepo to v1.54.2 [`#3713`](https://github.com/th-ch/youtube-music/pull/3713)
- chore(deps): update dependency vite to v7.1.2 [`#3710`](https://github.com/th-ch/youtube-music/pull/3710)
- chore(deps): update dependency @stylistic/eslint-plugin to v5.2.3 [`#3754`](https://github.com/th-ch/youtube-music/pull/3754)
- chore(deps): update dependency @babel/runtime to v7.28.3 [`#3753`](https://github.com/th-ch/youtube-music/pull/3753)
- chore(deps): update dependency discord-api-types to v0.38.20 [`#3706`](https://github.com/th-ch/youtube-music/pull/3706)
- fix(deps): update dependency @floating-ui/dom to v1.7.3 [`#3714`](https://github.com/th-ch/youtube-music/pull/3714)
- chore(deps): update dependency vite-plugin-solid to v2.11.8 [`#3711`](https://github.com/th-ch/youtube-music/pull/3711)
- chore(deps): update dependency rollup to v4.46.2 [`#3709`](https://github.com/th-ch/youtube-music/pull/3709)
- chore(deps): update dependency @electron/universal to v3.0.1 [`#3705`](https://github.com/th-ch/youtube-music/pull/3705)
- chore(deps): update dependency electron to v37.3.0 [`#3708`](https://github.com/th-ch/youtube-music/pull/3708)
- chore(docs): Grammar mistakes [`#3722`](https://github.com/th-ch/youtube-music/pull/3722)
- Fixes the error 500 for /auth/ endpoint [`#3627`](https://github.com/th-ch/youtube-music/pull/3627)
- feat: add custom window title option [`#3656`](https://github.com/th-ch/youtube-music/pull/3656)
- fix(deps): update dependency zod to v4.0.10 [`#3686`](https://github.com/th-ch/youtube-music/pull/3686)
- chore(deps): update dependency @babel/runtime to v7.28.2 [`#3687`](https://github.com/th-ch/youtube-music/pull/3687)
- chore(deps): update dependency rollup to v4.46.1 [`#3632`](https://github.com/th-ch/youtube-music/pull/3632)
- chore(deps): update dependency electron to v38.0.0-alpha.10 [`#3681`](https://github.com/th-ch/youtube-music/pull/3681)
- chore(deps): update dependency eslint-config-prettier to v10.1.8 [`#3676`](https://github.com/th-ch/youtube-music/pull/3676)
- chore(deps): update dependency eslint-plugin-prettier to v5.5.3 [`#3678`](https://github.com/th-ch/youtube-music/pull/3678)
- fix(deps): update dependency @ghostery/adblocker-electron to v2.11.3 [`#3679`](https://github.com/th-ch/youtube-music/pull/3679)
- chore(deps): update dependency discord-api-types to v0.38.17 [`#3620`](https://github.com/th-ch/youtube-music/pull/3620)
- chore(deps): update dependency esbuild to v0.25.8 [`#3675`](https://github.com/th-ch/youtube-music/pull/3675)
- chore(deps): update dependency @stylistic/eslint-plugin to v5.2.2 [`#3636`](https://github.com/th-ch/youtube-music/pull/3636)
- fix(deps): update dependency @hono/node-server to v1.17.1 [`#3625`](https://github.com/th-ch/youtube-music/pull/3625)
- fix(deps): update dependency hono to v4.8.7 [`#3567`](https://github.com/th-ch/youtube-music/pull/3567)
- chore(deps): update dependency typescript-eslint to v8.38.0 [`#3628`](https://github.com/th-ch/youtube-music/pull/3628)
- chore(deps): update dependency electron to v38.0.0-alpha.9 [`#3626`](https://github.com/th-ch/youtube-music/pull/3626)
- fix(Skip Disliked Song): updated querySelector [`#3667`](https://github.com/th-ch/youtube-music/pull/3667)
- chore(deps): update dependency vite to v7.0.11 [`#3624`](https://github.com/th-ch/youtube-music/pull/3624)
- fix(deps): update dependency @hono/zod-validator to v0.7.1 [`#3616`](https://github.com/th-ch/youtube-music/pull/3616)
- fix(discord-rpc, scrobbler): Align artist and title with the last.fm's de facto standard [`#3358`](https://github.com/th-ch/youtube-music/issues/3358) [`#3641`](https://github.com/th-ch/youtube-music/issues/3641)
- fix: fix #3621 (#3774) [`#3621`](https://github.com/th-ch/youtube-music/issues/3621)
- fix: fix #3661 [`#3661`](https://github.com/th-ch/youtube-music/issues/3661)
- fix: fix #3613, fix #3651 [`#3613`](https://github.com/th-ch/youtube-music/issues/3613) [`#3651`](https://github.com/th-ch/youtube-music/issues/3651)
- chore: remove unused deps [`2a81a4e`](https://github.com/th-ch/youtube-music/commit/2a81a4e887cb5cc3c91a672302db6da3c15544e8)
- chore(i18n): Translated using Weblate (Swedish) [`0a6f244`](https://github.com/th-ch/youtube-music/commit/0a6f244035f17724ab1934f3cb2e011adf838e09)
- fix: bump dependencies [`5ba65ea`](https://github.com/th-ch/youtube-music/commit/5ba65ea1221236478efa8cb61bca3ffb1bb2d061)
#### [v3.10.0](https://github.com/th-ch/youtube-music/compare/v3.9.0...v3.10.0)
> 13 July 2025
- fix(deps): update dependency butterchurn to v3.0.0-beta.5 [`#3610`](https://github.com/th-ch/youtube-music/pull/3610)
- chore(deps): update eslint monorepo to v9.31.0 [`#3600`](https://github.com/th-ch/youtube-music/pull/3600)
- chore(deps): update dependency rollup to v4.45.0 [`#3568`](https://github.com/th-ch/youtube-music/pull/3568)

View File

@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400">
<g transform="translate(183.604 196.396)" stroke="#fff" stroke-width="2.23">
<path
style="line-height:normal;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration-line:none;text-transform:none;block-progression:tb;marker:none"
d="M-116.99 106.245l31.82 31.82 236.31-236.31-31.82-31.82z" color="#000" font-weight="400"
font-family="Sans" overflow="visible" fill="#fff" stroke="none"/>
<circle r="171.304" cy="4" cx="16" fill="none" stroke-width="44.6"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 588 B

View File

@ -1,35 +0,0 @@
<svg width="1440" height="582" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="a">
<stop stop-color="#606483" stop-opacity="0" offset="0%"/>
<stop stop-color="#363636" stop-opacity=".72" offset="100%"/>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="39.334%" y2="79.282%" id="b">
<stop stop-color="#363636" offset="0%"/>
<stop stop-color="#363636" stop-opacity="0" offset="100%"/>
</linearGradient>
<radialGradient cx="33.3%" cy="43.394%" fx="33.3%" fy="43.394%" r="57.93%"
gradientTransform="matrix(.24796 -.96592 .92535 .25883 -.151 .643)" id="c">
<stop stop-color="#c3352e" stop-opacity="0" offset="0%"/>
<stop stop-color="#c3352e" stop-opacity=".64" offset="51.712%"/>
<stop stop-color="#c3352e" stop-opacity=".24" offset="100%"/>
</radialGradient>
<filter id="d">
<feTurbulence type="fractalNoise" numOctaves="2" baseFrequency=".3" result="turb"/>
<feComposite in="turb" operator="arithmetic" k1=".1" k2=".1" k3=".1" k4=".1" result="result1"/>
<feComposite operator="in" in="result1" in2="SourceGraphic" result="finalFilter"/>
<feBlend mode="multiply" in="finalFilter" in2="SourceGraphic"/>
</filter>
</defs>
<g fill="none" fill-rule="evenodd">
<path
d="M252.464 335.471c101.27 115.965 283.227-105.29 283.227-154.996 0-49.705-111.929-90-250-90s-250 40.295-250 90c0 49.706 115.503 39.032 216.773 154.996z"
fill="url(#a)" transform="rotate(24 -272.272 -82.087)"/>
<path
d="M302.512 242.909c88.025 32.428 156-25.04 156-55.93 0-30.888-69.844-55.928-156-55.928-86.157 0-156 25.04-156 55.929 0 30.888 67.974 23.5 156 55.929z"
fill="url(#b)" transform="rotate(24 -255.451 -119.868)"/>
<path
d="M103.064 315.218c128.156 12.998 192.38 157.059 218.627 106.632 26.247-50.427-44.059-106.456 60.397-202.707 104.457-96.252-143.2-285.785-172.392-122.551C180.503 259.825-25.091 302.22 103.064 315.218z"
transform="translate(1176 -33)" fill="url(#c)" filter="url(#d)"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 KiB

View File

@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 176 176" width="32" height="32">
<circle fill="red" cx="88" cy="88" r="88"/>
<path fill="#FFF"
d="M88 46c23.1 0 42 18.8 42 42s-18.8 42-42 42-42-18.8-42-42 18.9-42 42-42m0-4c-25.4 0-46 20.6-46 46s20.6 46 46 46 46-20.6 46-46-20.6-46-46-46z"/>
<path fill="#FFF" d="M72 111l39-24-39-22z"/>
</svg>

Before

Width:  |  Height:  |  Size: 360 B

View File

@ -5,7 +5,7 @@
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<title>YouTube Music Desktop App (Unofficial)</title>
<title>YTMD App (Unofficial)</title>
<link
href="./favicon/favicon.ico"
rel="icon"
@ -39,22 +39,22 @@
<meta content="#131313" name="theme-color"/>
<meta
content="YouTube Music Unofficial Desktop App with built-in ad blocker and downloader"
content="YTMD App"
name="description"
/>
<meta
content="YouTube&nbsp;Music&nbsp;Desktop&nbsp;App"
content="YTMD&nbsp;App"
property="og:site_name"
/>
<meta
class="meta-url"
content="https://th-ch.github.io/youtube-music"
content="https://ytmd-devs.github.io/ytmd"
property="og:url"
/>
<meta content="website" property="og:type"/>
<meta
class="meta-url"
content="https://th-ch.github.io/youtube-music"
content="https://ytmd-devs.github.io/ytmd"
name="twitter:url"
/>
@ -69,11 +69,11 @@
<div class="site-header-inner">
<div class="brand header-brand">
<h1 class="m-0">
<a href="https://github.com/th-ch/youtube-music">
<a href="https://github.com/ytmd-devs/ytmd">
<img
alt="YouTube Music"
alt="YTM"
class="header-logo-image"
src="./img/youtube-music.svg"
src="./img/ytmd.svg"
/>
</a>
</h1>
@ -92,17 +92,16 @@
<div class="hero-inner">
<div class="hero-copy">
<h1 class="hero-title mt-0">
Custom YouTube Music Desktop App
Custom YTMD App
</h1>
<p class="hero-paragraph">
Open source, cross-platform, unofficial YouTube Music Desktop
App with built-in <strong>ad blocker</strong> and
<strong>downloader</strong>
Open source, cross-platform, unofficial YTMD
App
</p>
<div class="hero-cta">
<a
class="button button-primary button-wide-mobile"
href="https://github.com/th-ch/youtube-music/releases/latest"
href="https://github.com/ytmd-devs/ytmd/releases/latest"
>Download</a
>
</div>
@ -110,9 +109,9 @@
<div class="mockup-container">
<div class="mockup-bg">
<img
alt="YouTube Music"
alt="YTM"
id="mockup-header-img"
src="./img/youtube-music.png"
src="./img/ytmd.png"
/>
</div>
</div>
@ -255,11 +254,6 @@
transform 0.6s cubic-bezier(0.215, 0.61, 0.355, 1) 0s;
"
>
<h3 class="mt-0 mb-16">Built-in downloader</h3>
<p class="m-0">
Download (like youtube-dl) to custom formats (mp3, opus,
etc) directly from the interface
</p>
</div>
</div>
<div class="feature-extended">
@ -428,7 +422,7 @@
<div class="cta-cta">
<a
class="button button-primary button-wide-mobile"
href="https://github.com/th-ch/youtube-music"
href="https://github.com/ytmd-devs/ytmd"
>Go to code</a
>
</div>
@ -441,28 +435,28 @@
<div class="container">
<div class="site-footer-inner">
<div class="brand footer-brand">
<a href="https://github.com/th-ch/youtube-music">
<img alt="YouTube Music logo" src="./img/youtube-music.svg"/>
<a href="https://github.com/ytmd-devs/ytmd">
<img alt="YTM logo" src="./img/ytmd.svg"/>
</a>
</div>
<ul class="footer-links list-reset">
<li>
<a href="https://github.com/th-ch/youtube-music">Main page</a>
<a href="https://github.com/ytmd-devs/ytmd">Main page</a>
</li>
<li>
<a href="https://github.com/th-ch/youtube-music/issues"
<a href="https://github.com/ytmd-devs/ytmd/issues"
>Issues</a
>
</li>
<li>
<a href="https://github.com/th-ch/youtube-music/pulls"
<a href="https://github.com/ytmd-devs/ytmd/pulls"
>Pull requests</a
>
</li>
</ul>
<ul class="footer-social-links list-reset">
<li>
<a href="https://github.com/th-ch/youtube-music">
<a href="https://github.com/ytmd-devs/ytmd">
<span class="screen-reader-text">GitHub</span>
<svg
height="16"
@ -478,7 +472,7 @@
</a>
</li>
</ul>
<div class="footer-copyright">© 2024 th-ch</div>
<div class="footer-copyright">© 2025 th-ch</div>
</div>
</div>
</div>

View File

@ -45,7 +45,7 @@ export default defineConfig({
formats: ['es'],
},
outDir: 'dist/main',
rollupOptions: {
rolldownOptions: {
external: ['electron', 'custom-electron-prompt', ...builtinModules],
input: './src/index.ts',
},
@ -96,7 +96,7 @@ export default defineConfig({
commonjsOptions: {
ignoreDynamicRequires: true,
},
rollupOptions: {
rolldownOptions: {
external: ['electron', 'custom-electron-prompt', ...builtinModules],
input: './src/preload.ts',
},
@ -149,7 +149,7 @@ export default defineConfig({
name: 'renderer',
},
outDir: 'dist/renderer',
rollupOptions: {
rolldownOptions: {
external: ['electron', ...builtinModules],
input: './src/index.html',
},

View File

@ -31,11 +31,19 @@ export default tsEslint.config(
rules: {
'stylistic/arrow-parens': ['error', 'always'],
'stylistic/object-curly-spacing': ['error', 'always'],
'stylistic/jsx-pascal-case': 'error',
'stylistic/jsx-curly-spacing': ['error', { when: 'never', children: true }],
'stylistic/jsx-sort-props': 'error',
'prettier/prettier': ['error', { singleQuote: true, semi: true, tabWidth: 2, trailingComma: 'all', quoteProps: 'preserve' }],
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-misused-promises': ['off', { checksVoidReturn: false }],
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/consistent-type-imports': ['error', {
fixStyle: 'inline-type-imports',
prefer: 'type-imports',
disallowTypeAnnotations: false,
}],
'importPlugin/first': 'error',
'importPlugin/newline-after-import': 'off',
'importPlugin/no-default-export': 'off',

View File

@ -2,7 +2,7 @@
"name": "youtube-music",
"desktopName": "com.github.th_ch.youtube_music",
"productName": "YouTube Music",
"version": "3.10.0",
"version": "3.11.0",
"description": "YouTube Music Desktop App - including custom plugins",
"main": "./dist/main/index.js",
"type": "module",
@ -14,16 +14,16 @@
"url": "https://github.com/th-ch/youtube-music"
},
"scripts": {
"test": "playwright test",
"test:debug": "cross-env DEBUG=pw:*,-pw:test:protocol playwright test",
"build": "electron-vite build",
"test": "pnpm playwright test",
"test:debug": "pnpm cross-env DEBUG=pw:*,-pw:test:protocol playwright test",
"build": "pnpm electron-vite build",
"vite:inspect": "pnpm clean && electron-vite build --mode development && pnpm exec serve .vite-inspect",
"start": "electron-vite preview",
"start:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm start",
"dev": "cross-env NODE_OPTIONS=--enable-source-maps electron-vite dev --watch",
"dev:renderer": "cross-env NODE_OPTIONS=--enable-source-maps electron-vite dev",
"dev:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm dev",
"clean": "del-cli dist && del-cli pack && del-cli .vite-inspect",
"start": "pnpm electron-vite preview",
"start:debug": "pnpm cross-env ELECTRON_ENABLE_LOGGING=1 pnpm start",
"dev": "pnpm cross-env NODE_ENV=development NODE_OPTIONS=--enable-source-maps electron-vite dev --watch",
"dev:renderer": "pnpm cross-env NODE_ENV=development NODE_OPTIONS=--enable-source-maps electron-vite dev",
"dev:debug": "pnpm cross-env ELECTRON_ENABLE_LOGGING=1 pnpm dev",
"clean": "pnpm del-cli dist && pnpm del-cli pack && pnpm del-cli .vite-inspect",
"dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never",
"dist:linux": "pnpm clean && pnpm build && pnpm electron-builder --linux -p never",
"dist:linux:deb-arm64": "pnpm clean && pnpm build && pnpm electron-builder --linux deb:arm64 -p never",
@ -32,12 +32,12 @@
"dist:mac:arm64": "pnpm clean && pnpm build && pnpm electron-builder --mac dmg:arm64 -p never",
"dist:win": "pnpm clean && pnpm build && pnpm electron-builder --win -p never",
"dist:win:x64": "pnpm clean && pnpm build && pnpm electron-builder --win nsis-web:x64 -p never",
"lint": "eslint .",
"changelog": "npx --yes auto-changelog",
"lint": "pnpm eslint ./src",
"changelog": "pnpm dlx auto-changelog",
"release:linux": "pnpm clean && pnpm build && pnpm electron-builder --linux -p always -c.snap.publish=github",
"release:mac": "pnpm clean && pnpm build && pnpm electron-builder --mac -p always",
"release:win": "pnpm clean && pnpm build && pnpm electron-builder --win -p always",
"typecheck": "tsc -p tsconfig.json --noEmit"
"typecheck": "pnpm tsc -p tsconfig.json --noEmit"
},
"engines": {
"node": ">=22",
@ -45,30 +45,34 @@
},
"pnpm": {
"overrides": {
"vite": "npm:rolldown-vite@7.1.2",
"node-gyp": "11.3.0",
"vite": "npm:rolldown-vite@7.1.8",
"node-gyp": "11.4.2",
"xml2js": "0.6.2",
"node-fetch": "3.3.2",
"@electron/universal": "3.0.1",
"@babel/runtime": "7.28.3"
"@babel/runtime": "7.28.4"
},
"patchedDependencies": {
"vudio@2.1.1": "patches/vudio@2.1.1.patch",
"@malept/flatpak-bundler@0.4.0": "patches/@malept__flatpak-bundler@0.4.0.patch",
"kuromoji@0.1.2": "patches/kuromoji@0.1.2.patch",
"file-type@16.5.4": "patches/file-type@16.5.4.patch"
"file-type@16.5.4": "patches/file-type@16.5.4.patch",
"electron-is@3.0.0": "patches/electron-is@3.0.0.patch"
},
"neverBuiltDependencies": []
},
"dependencies": {
"@dehoist/romanize-thai": "1.0.0",
"@electron-toolkit/tsconfig": "1.0.1",
"@electron/remote": "2.1.3",
"@ffmpeg.wasm/core-mt": "0.12.0",
"@ffmpeg.wasm/main": "0.12.0",
"@floating-ui/dom": "1.7.3",
"@floating-ui/dom": "1.7.4",
"@foobar404/wave": "2.0.5",
"@ghostery/adblocker-electron": "2.11.3",
"@ghostery/adblocker-electron-preload": "2.11.3",
"@hono/node-server": "1.18.2",
"@ghostery/adblocker-electron": "2.11.6",
"@ghostery/adblocker-electron-preload": "2.11.6",
"@hono/node-server": "1.19.1",
"@hono/node-ws": "1.2.0",
"@hono/swagger-ui": "0.5.2",
"@hono/zod-openapi": "1.1.0",
"@hono/zod-validator": "0.7.2",
@ -100,10 +104,10 @@
"filenamify": "6.0.0",
"hanja": "1.1.5",
"happy-dom": "18.0.1",
"hono": "4.9.2",
"hono": "4.9.6",
"howler": "2.2.4",
"html-to-text": "9.0.5",
"i18next": "25.3.6",
"i18next": "25.5.2",
"jimp": "1.6.0",
"keyboardevent-from-electron-accelerator": "2.0.0",
"keyboardevents-areequal": "0.2.2",
@ -115,7 +119,7 @@
"node-id3": "0.2.9",
"peerjs": "1.5.5",
"semver": "7.7.2",
"serve": "14.2.4",
"serve": "14.2.5",
"simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9",
"socks": "2.8.7",
"solid-element": "1.9.1",
@ -125,35 +129,35 @@
"solid-transition-group": "0.3.0",
"tiny-pinyin": "1.3.2",
"tinyld": "1.3.4",
"virtua": "0.41.5",
"virtua": "0.42.3",
"vudio": "2.1.1",
"x11": "2.3.0",
"youtubei.js": "15.0.1",
"zod": "4.0.17"
"zod": "4.1.5"
},
"devDependencies": {
"@electron-toolkit/tsconfig": "1.0.1",
"@eslint/js": "9.33.0",
"@eslint/js": "9.35.0",
"@malept/flatpak-bundler": "0.4.0",
"@playwright/test": "1.54.2",
"@stylistic/eslint-plugin": "5.2.3",
"@playwright/test": "1.55.0",
"@stylistic/eslint-plugin": "5.3.1",
"@total-typescript/ts-reset": "0.6.1",
"@types/electron-localshortcut": "3.1.3",
"@types/howler": "2.2.12",
"@types/html-to-text": "9.0.4",
"@types/semver": "7.7.0",
"@types/semver": "7.7.1",
"@types/trusted-types": "2.0.7",
"bufferutil": "4.0.9",
"builtin-modules": "5.0.0",
"cross-env": "10.0.0",
"del-cli": "6.0.0",
"discord-api-types": "0.38.20",
"electron": "37.3.0",
"discord-api-types": "0.38.23",
"electron": "38.0.0",
"electron-builder": "26.0.12",
"electron-builder-squirrel-windows": "26.0.12",
"electron-devtools-installer": "4.0.0",
"electron-vite": "4.0.0",
"eslint": "9.33.0",
"eslint": "9.35.0",
"eslint-config-prettier": "10.1.8",
"eslint-import-resolver-exports": "1.0.0-beta.5",
"eslint-import-resolver-typescript": "4.4.4",
@ -161,14 +165,14 @@
"eslint-plugin-prettier": "5.5.4",
"eslint-plugin-solid": "0.14.5",
"glob": "11.0.3",
"node-gyp": "11.3.0",
"playwright": "1.54.2",
"ts-morph": "26.0.0",
"node-gyp": "11.4.2",
"playwright": "1.55.0",
"ts-morph": "27.0.0",
"typescript": "5.9.2",
"typescript-eslint": "8.39.1",
"typescript-eslint": "8.43.0",
"utf-8-validate": "6.0.5",
"vite": "npm:rolldown-vite@7.1.2",
"vite-plugin-inspect": "11.3.2",
"vite": "npm:rolldown-vite@7.1.8",
"vite-plugin-inspect": "11.3.3",
"vite-plugin-resolve": "2.5.2",
"vite-plugin-solid": "2.11.8",
"ws": "8.18.3"

View File

@ -0,0 +1,27 @@
diff --git a/is.d.ts b/is.d.ts
index fb861f7b401914f0f89cb4edf25c51df5cb05812..82144733cd34d88e2deb2e4713b104418e673f2e 100644
--- a/is.d.ts
+++ b/is.d.ts
@@ -5,6 +5,7 @@ declare namespace is {
export function macOS(): boolean;
export function windows(): boolean;
export function linux(): boolean;
+ export function freebsd(): boolean;
export function x86(): boolean;
export function x64(): boolean;
export function production(): boolean;
diff --git a/is.js b/is.js
index a76bb1755a2728bde185b35d847031d3b8ea4ab0..f6b03406c17342f5af078de069e5bbbd2246e152 100644
--- a/is.js
+++ b/is.js
@@ -39,6 +39,10 @@ module.exports = {
linux: function () {
return process.platform === 'linux'
},
+ // Checks if we are under FreeBSD OS
+ freebsd: function () {
+ return process.platform === "freebsd"
+ },
// Checks if we are the processor's arch is x86
x86: function () {
return process.arch === 'ia32'

975
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,7 @@ export interface DefaultConfig {
likeButtons: string;
proxy: string;
startingPage: string;
backgroundMaterial?: 'none' | 'mica' | 'acrylic' | 'tabbed';
overrideUserAgent: boolean;
usePodcastParticipantAsArtist: boolean;
themes: string[];
@ -39,7 +40,7 @@ export interface DefaultConfig {
'plugins': Record<string, unknown>;
}
const defaultConfig: DefaultConfig = {
export const defaultConfig: DefaultConfig = {
'window-size': {
width: 1100,
height: 550,
@ -73,5 +74,3 @@ const defaultConfig: DefaultConfig = {
},
'plugins': {},
};
export default defaultConfig;

View File

@ -1,30 +1,36 @@
import { deepmergeCustom } from 'deepmerge-ts';
import defaultConfig from './defaults';
import store, { IStore } from './store';
import plugins from './plugins';
import { store, type IStore } from './store';
import { restart } from '@/providers/app-controls';
import type { defaultConfig } from './defaults';
const deepmerge = deepmergeCustom({
mergeArrays: false,
});
const set = (key: string, value: unknown) => {
export { defaultConfig } from './defaults';
export * as plugins from './plugins';
export const set = (key: string, value: unknown) => {
store.set(key, value);
};
const setPartial = (key: string, value: object, defaultValue?: object) => {
export const setPartial = (
key: string,
value: object,
defaultValue?: object,
) => {
const newValue = deepmerge(defaultValue ?? {}, store.get(key) ?? {}, value);
store.set(key, newValue);
};
function setMenuOption(key: string, value: unknown) {
export const setMenuOption = (key: string, value: unknown) => {
set(key, value);
if (store.get('options.restartOnConfigChanges')) {
restart();
}
}
};
// MAGIC OF TYPESCRIPT
@ -74,18 +80,11 @@ type PathValue<T, K extends string> =
? PathValue<T[A], B>
: T;
const get = <Key extends Paths<typeof defaultConfig>>(key: Key) =>
export const get = <Key extends Paths<typeof defaultConfig>>(key: Key) =>
store.get(key) as PathValue<typeof defaultConfig, typeof key>;
export default {
defaultConfig,
get,
set,
setPartial,
setMenuOption,
edit: () => store.openInEditor(),
watch(cb: Parameters<IStore['onDidAnyChange']>[0]) {
store.onDidAnyChange(cb);
},
plugins,
export const edit = () => store.openInEditor();
export const watch = (cb: Parameters<IStore['onDidAnyChange']>[0]) => {
store.onDidAnyChange(cb);
};

View File

@ -1,7 +1,7 @@
import { deepmerge } from 'deepmerge-ts';
import { allPlugins } from 'virtual:plugins';
import store from './store';
import { store } from './store';
import { restart } from '@/providers/app-controls';
@ -68,13 +68,3 @@ export function enable(plugin: string) {
export function disable(plugin: string) {
setMenuOptions(plugin, { enabled: false }, []);
}
export default {
isEnabled,
getPlugins,
enable,
disable,
setOptions,
setMenuOptions,
getOptions,
};

View File

@ -1,6 +1,6 @@
import Store from 'electron-store';
import defaults from './defaults';
import { defaultConfig as defaults } from './defaults';
import { DefaultPresetList, type Preset } from '@/plugins/downloader/types';
@ -257,7 +257,7 @@ const migrations = {
},
};
export default new Store({
export const store = new Store({
defaults: {
...defaults,
// README: 'plugin' uses deepmerge to populate the default values, so it is not necessary to include it here

View File

@ -1,5 +1,5 @@
declare module 'custom-electron-prompt' {
import { BrowserWindow } from 'electron';
import { type BrowserWindow } from 'electron';
export type SelectOptions = Record<string, string>;

View File

@ -150,6 +150,12 @@
"visual-tweaks": {
"label": "تعديلات المظهر",
"submenu": {
"custom-window-title": {
"label": "عنوان نافذة مخصص",
"prompt": {
"placeholder": "مثال: YouTube Music"
}
},
"like-buttons": {
"default": "الافتراضي",
"force-show": "اجبار الظهور",
@ -414,6 +420,11 @@
}
}
},
"custom-output-device": {
"menu": {
"device-selector": "اختر جهاز"
}
},
"disable-autoplay": {
"description": "يجعل الأغنية تبدأ في وضع \"الإيقاف المؤقت\"",
"menu": {
@ -846,6 +857,18 @@
"description": "يضيف أداة TouchBar لمستخدمي macOS",
"name": "شريط اللمس (TouchBar)"
},
"transparent-player": {
"menu": {
"type": {
"submenu": {
"acrylic": "زجاجي",
"mica": "حجري",
"none": "لاشيء"
}
}
},
"name": "مشغل شفاف"
},
"tuna-obs": {
"description": "التكامل مع الإضافة\" Tuna\" الخاصة بـ OBS",
"name": "إضافة Tuna OBS"

View File

@ -150,6 +150,13 @@
"visual-tweaks": {
"label": "Визуални настройки",
"submenu": {
"custom-window-title": {
"label": "Персонализирано заглавие на прозорец",
"prompt": {
"label": "Въведи персонализирано заглавие: (остави празно за да изключиш)",
"placeholder": "Пример: Youtube Music"
}
},
"like-buttons": {
"default": "По подразбиране",
"force-show": "Принудително показване",
@ -414,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Конфигуриране на изходно медийно устройство за песни",
"menu": {
"device-selector": "Избери устройство"
},
"name": "Персонализирано изходно устройство",
"prompt": {
"device-selector": {
"label": "Избери изходното медийно устройство",
"title": "Избери изходно устройство"
}
}
},
"disable-autoplay": {
"description": "Започва песента в паузиран режим",
"menu": {
@ -437,7 +457,15 @@
"hide-duration-left": "Скрий оставащото време",
"hide-github-button": "Скрий бутона за линк към GitHub",
"play-on-youtube-music": "Възпроизведи в YouTube Music",
"set-inactivity-timeout": "Задай таймаут за неактивност"
"set-inactivity-timeout": "Задай таймаут за неактивност",
"set-status-display-type": {
"label": "Статус текст",
"submenu": {
"artist": "Слушам {artist}",
"title": "Слушам {song title}",
"youtube-music": "Слушам YouTube Music"
}
}
},
"name": "Дискорд Разширен статус",
"prompt": {
@ -729,6 +757,7 @@
"listenbrainz": {
"token": "Въведете ListenBrainz потребителски токен"
},
"scrobble-alternative-artist": "Използвай алтернативни изпълнители",
"scrobble-alternative-title": "Използвай алтернативни заглавия",
"scrobble-other-media": "Скробъл на други медии"
},
@ -814,6 +843,14 @@
"label": "Направете текстовете перфектно синхронизирани",
"tooltip": "Изчислете до милисекунда показването на следващия ред (може да има малък ефект върху производителността)"
},
"preferred-provider": {
"label": "Предпочитан доставчик",
"none": {
"label": "Празно",
"tooltip": "Без предпочитан доставчик"
},
"tooltip": "Изберете доставчик по подразбиране"
},
"romanization": {
"label": "Романизиране на текстовете",
"tooltip": "Ако текстовете са на друг език, опитайте да покажете латинска версия."
@ -846,6 +883,27 @@
"description": "Добавя уиджет за TouchBar за потребители на macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Прави прозореца на приложението прозрачен",
"menu": {
"opacity": {
"label": "Прозрачност",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Тип",
"submenu": {
"acrylic": "Акрил",
"mica": "Слюда",
"none": "Празно",
"tabbed": "С раздели"
}
}
},
"name": "Прозрачен плейър"
},
"tuna-obs": {
"description": "Интеграция с плъгина Tuna за OBS",
"name": "Tuna OBS"

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Configura un dispositiu multimèdia de sortida personalitzat per a cançons",
"menu": {
"device-selector": "Selecciona un dispositiu"
},
"name": "Dispositiu de sortida personalitzat",
"prompt": {
"device-selector": {
"label": "Trieu el dispositiu de sortida que s'utilitzarà",
"title": "Escull el dispositiu de sortida"
}
}
},
"disable-autoplay": {
"description": "Fa que la cançó comenci en mode «pausat»",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "Amaga la durada restant",
"hide-github-button": "Amaga el botó de l'enllaç a GitHub",
"play-on-youtube-music": "Reprodueix a YouTube Music",
"set-inactivity-timeout": "Estableix temps d'espera d'inactivitat"
"set-inactivity-timeout": "Estableix temps d'espera d'inactivitat",
"set-status-display-type": {
"label": "Text d'estat",
"submenu": {
"artist": "Escoltant {artist}",
"title": "Escoltant {song title}",
"youtube-music": "Escoltant YouTube Music"
}
}
},
"name": "Estat d'activitat de Discord",
"prompt": {
@ -615,10 +636,10 @@
"name": "Navegació",
"templates": {
"back": {
"title": "Tornar a la pàgina anterior"
"title": "Pàgina anterior"
},
"forward": {
"title": "Anar a la pàgina següent"
"title": "Pàgina següent"
}
}
},
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "Introduir token d'usuari de ListenBrainz"
},
"scrobble-alternative-artist": "Utilitza artistes alternatius",
"scrobble-alternative-title": "Useu títols alternatius",
"scrobble-other-media": "Scrobble amb altres mitjans"
},
@ -821,6 +843,14 @@
"label": "Fes que les lletres es sincronitzin a la perfecció",
"tooltip": "Calcula al mil·lisegon l'aparició de la següent línia (pot tenir un petit impacte en el rendiment)"
},
"preferred-provider": {
"label": "Proveïdor preferit",
"none": {
"label": "Cap",
"tooltip": "Cap proveïdor preferit"
},
"tooltip": "Trieu el proveïdor predeterminat que voleu utilitzar"
},
"romanization": {
"label": "Romanitza les lletres",
"tooltip": "Si les lletres són en un idioma diferent, intenta mostrar la versió amb alfabet llatí."
@ -853,6 +883,27 @@
"description": "Afegeix un giny a la Touch Bar per usuaris de macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Fa la finestra de l'aplicació transparent",
"menu": {
"opacity": {
"label": "Opacitat",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tipus",
"submenu": {
"acrylic": "Acrílic",
"mica": "Mica",
"none": "Cap",
"tabbed": "En pestanyes"
}
}
},
"name": "Reproductor Transparent"
},
"tuna-obs": {
"description": "Integració amb l'extensió «Tuna» del OBS",
"name": "Tuna OBS"

View File

@ -8,7 +8,7 @@
"load-all": "Načítání všech pluginů",
"load-failed": "Selhalo načtení \"{{pluginName}}\" pluginu",
"loaded": "Plugin \"{{pluginName}}\" načten",
"unload-failed": "Selhalo unload \"{{pluginName}}\" pluginu",
"unload-failed": "Selhalo vypnutí \"{{pluginName}}\" pluginu",
"unloaded": "Plugin {{pluginName}} byl odnačten"
}
}
@ -288,6 +288,7 @@
},
"amuse": {
"description": "Přídá YouTube Music podporu pro Amuse právě hraje widget od 6K Labs",
"name": "Amuse",
"response": {
"query": "Server Amuse API běží. Pošli požadavek typu GET na /query, aby ses dozvěděl info o písničce."
}
@ -351,6 +352,7 @@
"label": "Port"
}
},
"name": "Autorizační Proxy adaptér",
"prompt": {
"hostname": {
"label": "Zadejte hostname lokálního proxy serveru (vyžaduje restart):",
@ -419,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Nastavte vlastní výstupní zařízení pro skladby",
"menu": {
"device-selector": "Vyberte zařízení"
},
"name": "Vlastní výstupní zařízení",
"prompt": {
"device-selector": {
"label": "Vyberte zařízení pro výstup zvuku",
"title": "Vyberte výstupní zařízení"
}
}
},
"disable-autoplay": {
"description": "Spustí písničku v režimu \"pozastaveno\"",
"menu": {
@ -442,7 +457,15 @@
"hide-duration-left": "Skrýt zbývající duration",
"hide-github-button": "Skrýt tlačítko s odkazem na GitHub",
"play-on-youtube-music": "Hrát na YouTube Music",
"set-inactivity-timeout": "Nastavit timeout pro neaktivitu"
"set-inactivity-timeout": "Nastavit timeout pro neaktivitu",
"set-status-display-type": {
"label": "Text statusu",
"submenu": {
"artist": "Poslouchám: {artist}",
"title": "Poslouchám {song title}",
"youtube-music": "Poslouchám YouTube Music"
}
}
},
"name": "Discord Rich Persence",
"prompt": {
@ -643,7 +666,8 @@
"name": "Oznámení"
},
"performance-improvement": {
"description": "Zlepšit výkon povolením experimentálních skriptů"
"description": "Zlepšit výkon povolením experimentálních skriptů",
"name": "Zlepšení výkonu [Beta]"
},
"picture-in-picture": {
"description": "Povoluje switch aplikaci do režimu obrázek v obrázku",
@ -733,6 +757,7 @@
"listenbrainz": {
"token": "Vložte Listenbrainz user token"
},
"scrobble-alternative-artist": "Použij alternativní umělce",
"scrobble-alternative-title": "Používat alternativní názvy",
"scrobble-other-media": "Scrobble jiné média"
},
@ -795,20 +820,44 @@
"line-effect": {
"label": "Efekt řádku",
"submenu": {
"fancy": {
"label": "Luxusní",
"tooltip": "Použijte velké, aplikací inspirované efekty na aktuální řádek"
},
"focus": {
"label": "Soustředění",
"tooltip": "Nechat pouze aktuální řádek bílý"
},
"offset": {
"label": "Posun"
"label": "Posun",
"tooltip": "Posunout aktuální řádek doprava"
},
"scale": {
"label": "Zvětšení",
"tooltip": "Změnit velikost aktuálního řádku"
}
}
},
"tooltip": "Vyberte efekt pro aktuální řádek"
},
"precise-timing": {
"label": "Dokonale synchronizovat texty",
"tooltip": "Vypočítat zobrazení dalšího řádku na milisekundu (může mít menší dopad na výkon)"
},
"preferred-provider": {
"label": "Preferovaný poskytovatel",
"none": {
"label": "Žádný",
"tooltip": "Žádný preferovaný poskytovatel"
},
"tooltip": "Zvolte výchozího poskytovatele"
},
"romanization": {
"label": "Romanizovat texty",
"tooltip": "Pokud je text v jiném jazyce, zkusit zobrazit verzi v latince."
},
"show-lyrics-even-if-inexact": {
"label": "Zobrazit i nepřesné texty"
"label": "Zobrazit i nepřesné texty",
"tooltip": "Pokud se píseň nenajde, plugin to zkusí znovu s jiným vyhledávacím výrazem.\nVýsledek druhého pokusu nemusí být přesný."
},
"show-time-codes": {
"label": "Zobrazit časové kódy",
@ -817,7 +866,8 @@
},
"name": "Synchronizované texty",
"refetch-btn": {
"fetching": "Získávání..."
"fetching": "Získávání...",
"normal": "Znovu načíst texty"
},
"warnings": {
"duration-mismatch": "⚠️ - Text nemusí být synchronizován kvůli neshodě v délce trvání.",
@ -833,10 +883,35 @@
"description": "Přidává Touch Bar widget pro macOS uživatele",
"name": "Touch Bar"
},
"transparent-player": {
"description": "Zprůhlední okno aplikace",
"menu": {
"opacity": {
"label": "Průhlednost",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Typ",
"submenu": {
"acrylic": "Akryl",
"mica": "Mica",
"none": "Žádné",
"tabbed": "Záložkovaný"
}
}
},
"name": "Průhledný přehrávač"
},
"tuna-obs": {
"description": "Integrace s OBS's plugin Tuna",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Zabrání tomu, aby se přehrávač objevil při hraní písně",
"name": "Nepřekážející přehrávač"
},
"video-toggle": {
"description": "Přidává tlačítko k switch mezi video/písničko režimem. Může také odstranit celou video kartu",
"menu": {

View File

@ -8,7 +8,8 @@
"load-all": "Indlæser alle plugins",
"load-failed": "Fejl ved indlæsning af plugin \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" indlæst",
"unload-failed": "Fejl ved unload af plugin \"{{pluginName}}\""
"unload-failed": "Fejl ved aflæsning af plugin \"{{pluginNavn}}\"",
"unloaded": "Plugin \"{{pluginNavn}}\" aflæssede"
}
}
},
@ -98,6 +99,7 @@
"auto-reset-app-cache": "Nulstil app cache når appen starter",
"disable-hardware-acceleration": "Deaktiver hardware acceleration",
"edit-config-json": "Rediger config.json",
"override-user-agent": "Erstat Bruger-Agent",
"restart-on-config-changes": "Genstart ved config ændringer",
"set-proxy": {
"label": "Indstil proxy",
@ -130,22 +132,34 @@
}
},
"resume-on-start": "Genoptag sidste sang når appen starter",
"single-instance-lock": "Enkeltinstans lås",
"start-at-login": "Start ved login",
"starting-page": {
"label": "Startside",
"unset": "Ikke valgt"
},
"tray": {
"label": "Bakke",
"submenu": {
"disabled": "Deaktiveret",
"enabled-and-hide-app": "Bakke aktiveret, og skjul programvindue",
"enabled-and-show-app": "Aktiver og vis app",
"play-pause-on-click": "Start/Stop ved klik"
}
},
"visual-tweaks": {
"label": "Visuelle Justeringer",
"submenu": {
"custom-window-title": {
"label": "Tilpasset vindues titel",
"prompt": {
"label": "Indtast tilpasset vindues titel: (lad være top for deaktiveret)",
"placeholder": "Eksempel: YouTube Music"
}
},
"like-buttons": {
"default": "Standard",
"force-show": "Tving visning",
"hide": "Skjul",
"label": "Like knapper"
},
@ -153,11 +167,15 @@
"theme": {
"dialog": {
"button": {
"cancel": "Annuller",
"remove": "Fjern"
}
},
"remove-theme": "Er du sikker på at du til fjerne det brugerdefinerede tema?",
"remove-theme-message": "Dette vil fjerne det brugerdefinerede tema"
},
"label": "Tema",
"submenu": {
"import-css-file": "Importer brugerdefinerede CSS fil",
"no-theme": "Intet tema"
}
}
@ -173,6 +191,7 @@
"view": {
"label": "Vis",
"submenu": {
"force-reload": "Tving Genindlæs",
"reload": "Genindlæs",
"zoom-in": "Zoom ind",
"zoom-out": "Zoom ud"

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Einen maßgeschneiderten Ausgabemedienträger für Lieder einrichten",
"menu": {
"device-selector": "Gerät auswählen"
},
"name": "Benutzerdefiniertes Ausgabegerät",
"prompt": {
"device-selector": {
"label": "Wähle das Ausgabegerät, welches benutzt werden soll",
"title": "Wähle ein Ausgabegerät"
}
}
},
"disable-autoplay": {
"description": "Startet Lied im pausierten Modus",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "Verbleibende Zeit verstecken",
"hide-github-button": "Knopf mit Link zu GitHub ausblenden",
"play-on-youtube-music": "Auf YouTube Music abspielen",
"set-inactivity-timeout": "Inaktivitätstimeout setzen"
"set-inactivity-timeout": "Inaktivitätstimeout setzen",
"set-status-display-type": {
"label": "Status Text",
"submenu": {
"artist": "Hört {artist} zu",
"title": "Du hörst {song title}",
"youtube-music": "Hört YouTube Music"
}
}
},
"name": "Discords Aktivitätsstatus",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "ListenBrainz-Benutzer-Token eintragen"
},
"scrobble-alternative-artist": "Benutze Alternative Künstler",
"scrobble-alternative-title": "Nutze alternative Titel",
"scrobble-other-media": "Andere Medien scrobbeln"
},
@ -821,6 +843,14 @@
"label": "Den Songtext perfekt synchronisieren",
"tooltip": "Auf die Millisekunde genau berechnen, wann die nächste Zeile angezeigt werden soll (Kann Einfluss auf die Leistung haben)"
},
"preferred-provider": {
"label": "bevorzugter Anbieter",
"none": {
"label": "Nichts",
"tooltip": "Kein bevorzugter Anbieter"
},
"tooltip": "Standardanbieter auswählen"
},
"romanization": {
"label": "Lateinische Umschrift anzeigen",
"tooltip": "Wenn der Liedtext in einer anderen Schrift ist, zeige nach Möglichkeit eine Version in lateinischer Schrift an."
@ -853,6 +883,27 @@
"description": "Fügt ein TouchBar-Widget für macOS-Benutzer hinzu",
"name": "TouchBar"
},
"transparent-player": {
"description": "Macht das Player-Fenster transparent",
"menu": {
"opacity": {
"label": "Hintergrund-Sichtbarkeit",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Typ",
"submenu": {
"acrylic": "Acrylic",
"mica": "Mica",
"none": "Nichts",
"tabbed": "Tabbed"
}
}
},
"name": "Transparent Player"
},
"tuna-obs": {
"description": "Integration mit dem OBS-Plugin Tuna",
"name": "Tuna OBS"

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Configure a custom output media device for songs",
"menu": {
"device-selector": "Select Device"
},
"name": "Custom Output Device",
"prompt": {
"device-selector": {
"label": "Choose the output media device to be used",
"title": "Select Output Device"
}
}
},
"disable-autoplay": {
"description": "Makes song start in \"paused\" mode",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "Hide duration left",
"hide-github-button": "Hide GitHub link Button",
"play-on-youtube-music": "Play on YouTube Music",
"set-inactivity-timeout": "Set inactivity timeout"
"set-inactivity-timeout": "Set inactivity timeout",
"set-status-display-type": {
"label": "Status text",
"submenu": {
"youtube-music": "Listening to YouTube Music",
"artist": "Listening to {artist}",
"title": "Listening to {song title}"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
@ -737,6 +758,7 @@
"token": "Enter ListenBrainz user token"
},
"scrobble-alternative-title": "Use alternative titles",
"scrobble-alternative-artist": "Use alternative artists",
"scrobble-other-media": "Scrobble other media"
},
"name": "Scrobbler",
@ -791,6 +813,14 @@
"not-found": "⚠️ No lyrics found for this song."
},
"menu": {
"preferred-provider": {
"label": "Preferred Provider",
"tooltip": "Choose the default provider to use",
"none": {
"label": "None",
"tooltip": "No preferred provider"
}
},
"default-text-string": {
"label": "Default character between lyrics",
"tooltip": "Choose the default character to use for the gap between lyrics"
@ -853,6 +883,27 @@
"description": "Adds a TouchBar widget for macOS users",
"name": "TouchBar"
},
"transparent-player": {
"description": "Makes the app window transparent",
"name": "Transparent Player",
"menu": {
"opacity": {
"label": "Opacity",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Type",
"submenu": {
"acrylic": "Acrylic",
"mica": "Mica",
"tabbed": "Tabbed",
"none": "None"
}
}
}
},
"tuna-obs": {
"description": "Integration with OBS's plugin Tuna",
"name": "Tuna OBS"

View File

@ -2,14 +2,14 @@
"common": {
"console": {
"plugins": {
"execute-failed": "Error al ejecutar el plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} Ejecutó en {{ms}}ms",
"initialize-failed": "Error al inicializar el plugin \"{{pluginName}}\"",
"load-all": "Cargando todos los plugins",
"load-failed": "Error al cargar el plugin \"{{pluginName}}\"",
"loaded": "Plugin \"{{pluginName}}\" cargado",
"unload-failed": "No se ha podido descargar el plugin \"{{pluginName}}\"",
"unloaded": "Plugin \"{{pluginName}}\" descargado"
"execute-failed": "Error al ejecutar el complemento {{pluginName}}::{{contextName}}",
"executed-at-ms": "Complemento {{pluginName}}::{{contextName}} Ejecutó en {{ms}}ms",
"initialize-failed": "Error al inicializar el complemento \"{{pluginName}}\"",
"load-all": "Cargando todos los complementos",
"load-failed": "Error al cargar el complemento \"{{pluginName}}\"",
"loaded": "Complementos \"{{pluginName}}\" cargado",
"unload-failed": "No se ha podido descargar el complemento \"{{pluginName}}\"",
"unloaded": "Complemento \"{{pluginName}}\" descargado"
}
}
},
@ -53,7 +53,7 @@
"later": "Más tarde",
"restart-now": "Reiniciar ahora"
},
"detail": "El plugin \"{{pluginName}}\" requiere reiniciar para tomar efecto",
"detail": "El complemento \"{{pluginName}}\" requiere reiniciar para tomar efecto",
"message": "\"{{pluginName}}\" necesita reiniciar",
"title": "Se requiere reinicio"
},
@ -185,7 +185,7 @@
},
"plugins": {
"enabled": "Habilitado",
"label": "Plugins",
"label": "Complementos",
"new": "NUEVO"
},
"view": {
@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Configura un dispositivo de salida de audio personalizado para las canciones",
"menu": {
"device-selector": "Seleccionar un dispositivo"
},
"name": "Dispositivo de audio personalizado",
"prompt": {
"device-selector": {
"label": "Escoge el dispositivo de salida de audio que se va a usar",
"title": "Seleccionar un dispositivo de audio"
}
}
},
"disable-autoplay": {
"description": "Hace que la canción comience en modo \"pausado\"",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "Ocultar la duración restante",
"hide-github-button": "Ocultar el botón de enlace a GitHub",
"play-on-youtube-music": "Reproducir en YouTube Music",
"set-inactivity-timeout": "Establecer tiempo de inactividad"
"set-inactivity-timeout": "Establecer tiempo de inactividad",
"set-status-display-type": {
"label": "Texto de estado",
"submenu": {
"artist": "Escuchando a {artist}",
"title": "Escuchando {song title}",
"youtube-music": "Escuchando YouTube Music"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
@ -469,7 +490,7 @@
"ok": "OK"
},
"detail": "({{playlistSize}} canciones)",
"message": "Descargando Playlist {{playlistTitle}}",
"message": "Descargando lista de reproducción {{playlistTitle}}",
"title": "Descarga iniciada"
}
},
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "Introduzca el token de usuario de ListenBrainz"
},
"scrobble-alternative-artist": "Usar artistas alternativos",
"scrobble-alternative-title": "Usar títulos alternativos",
"scrobble-other-media": "Hacer Scrobble sobre otros medios"
},
@ -821,6 +843,14 @@
"label": "Haz que la letra esté perfectamente sincronizada",
"tooltip": "Calcular al milisegundo la visualización de la siguiente línea (puede tener un pequeño impacto en el rendimiento)"
},
"preferred-provider": {
"label": "Proveedor preferido",
"none": {
"label": "Ninguno",
"tooltip": "Ningún proveedor preferido"
},
"tooltip": "Elige el proveedor predeterminado que deseas usar"
},
"romanization": {
"label": "Romanizar letras",
"tooltip": "Si la letra está en un idioma diferente, intenta mostrar una versión en latín."
@ -853,8 +883,29 @@
"description": "Añade un widget TouchBar para los usuarios de macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Hace que la ventana de la aplicación sea transparente",
"menu": {
"opacity": {
"label": "Opacidad",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tipo",
"submenu": {
"acrylic": "Acrílico",
"mica": "Mica",
"none": "Ninguno",
"tabbed": "Con pestañas"
}
}
},
"name": "Reproductor transparente"
},
"tuna-obs": {
"description": "Integración con el plugin Tuna de OBS",
"description": "Integración con el complemento Tuna de OBS",
"name": "Tuna OBS"
},
"unobtrusive-player": {

View File

@ -150,6 +150,13 @@
"visual-tweaks": {
"label": "Mga Biswal na Tweak",
"submenu": {
"custom-window-title": {
"label": "Custom na window title",
"prompt": {
"label": "I-enter ang custom na window tile: (iwanang blanko para ma-disable)",
"placeholder": "Halimbawa: YouTube Music"
}
},
"like-buttons": {
"default": "Default",
"force-show": "Pilitang ipakita",
@ -378,6 +385,19 @@
}
}
},
"custom-output-device": {
"description": "I-configure ang custom na output media device para sa mga kanta",
"menu": {
"device-selector": "Pumili ng Device"
},
"name": "Custom na Output Device",
"prompt": {
"device-selector": {
"label": "Pumili ng output media device na gagamitin",
"title": "Pumili ng Output Device"
}
}
},
"disable-autoplay": {
"description": "Gawing simulan ang kanta sa \"naka-pause\" na mode",
"menu": {
@ -401,7 +421,14 @@
"hide-duration-left": "Itago ang natitirang oras",
"hide-github-button": "Itago ang button na GitHub link",
"play-on-youtube-music": "Patugtugin sa YouTube Music",
"set-inactivity-timeout": "I-set ang inactivity timeout"
"set-inactivity-timeout": "I-set ang inactivity timeout",
"set-status-display-type": {
"submenu": {
"artist": "Nakikinig sa {artist}",
"title": "Nakikinig sa {song title}",
"youtube-music": "Kumikinig sa YouTube Music"
}
}
},
"prompt": {
"set-inactivity-timeout": {
@ -649,6 +676,7 @@
"listenbrainz": {
"token": "Ilagay ang user token ng ListenBrainz"
},
"scrobble-alternative-artist": "Gumamit ng mga alternatibong artist",
"scrobble-alternative-title": "Gumamit ng alternatibong mga title",
"scrobble-other-media": "Mag-Scrobble ng ibang media"
},
@ -728,6 +756,14 @@
"label": "Gawing perpektong naka-sync ang lyrics",
"tooltip": "Kalkulahin sa millisecond ang pagpapakita ng susunod na linya (maaaring magkaroon ng maliit na epekto sa performance)"
},
"preferred-provider": {
"label": "Napiling Provider",
"none": {
"label": "Wala",
"tooltip": "Walang napiling provider"
},
"tooltip": "Pumili ng default na provider para gagamitin"
},
"romanization": {
"label": "I-romanize ang lyrics",
"tooltip": "Kung ang lyrics ay nasa ibang wika, subukang magpakita ng latin na bersyon."
@ -758,6 +794,19 @@
"touchbar": {
"description": "Idaragdag ang TouchBar na widget para sa mga user ng macOS"
},
"transparent-player": {
"description": "Gawing transparent ang app window",
"menu": {
"type": {
"label": "Uri",
"submenu": {
"none": "Wala",
"tabbed": "Naka-tab"
}
}
},
"name": "Transparent na Player"
},
"tuna-obs": {
"description": "Integrasyon kasama ang Tuna na OBS plugin"
},

View File

@ -79,7 +79,7 @@
}
},
"menu": {
"about": -propos",
"about": propos",
"navigation": {
"label": "Navigation",
"submenu": {
@ -94,7 +94,7 @@
"label": "Paramètres",
"submenu": {
"advanced-options": {
"label": "Options avancée",
"label": "Options avancées",
"submenu": {
"auto-reset-app-cache": "Réinitialiser le cache de l'application au démarrage",
"disable-hardware-acceleration": "Désactiver les accélérations matérielles",
@ -189,7 +189,7 @@
"new": "NOUVEAU"
},
"view": {
"label": "Vue",
"label": "Fenêtre",
"submenu": {
"force-reload": "Forcer l'actualisation",
"reload": "Actualiser",
@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Configurer un périphérique de sortie personnalisé pour les morceaux",
"menu": {
"device-selector": "Sélectionner un appareil"
},
"name": "Périphérique de sortie personnalisé",
"prompt": {
"device-selector": {
"label": "Choisissez le périphérique de sortie à utiliser",
"title": "Sélectionner le périphérique de sortie"
}
}
},
"disable-autoplay": {
"description": "Fait démarrer la chanson en mode \"pause\"",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "Masquer la durée restante",
"hide-github-button": "Masquer le bouton du lien GitHub",
"play-on-youtube-music": "Jouer sur YouTube Music",
"set-inactivity-timeout": "Définir le délai d'inactivité"
"set-inactivity-timeout": "Définir le délai d'inactivité",
"set-status-display-type": {
"label": "Texte d'état",
"submenu": {
"artist": "Écoute {artiste}",
"title": "Écoute {titre de la chanson}",
"youtube-music": "Écoute YouTube Music"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "Entrer le token utilisateur de ListenBrainz"
},
"scrobble-alternative-artist": "Utilisez d'autres artistes",
"scrobble-alternative-title": "Utiliser des titres alternatifs",
"scrobble-other-media": "Scrobbler d'autres médias"
},
@ -743,7 +765,7 @@
"prompt": {
"lastfm": {
"api-key": "Clé API de Last.fm",
"api-secret": "API secret de Last.fm"
"api-secret": "Secret de l'API de Last.fm"
},
"listenbrainz": {
"token": {
@ -774,7 +796,7 @@
},
"skip-disliked-songs": {
"description": "Passer les musiques que je n'aime pas",
"name": "Passer Chansons Déplaisantes"
"name": "Passer les chansons « Je n'aime pas »"
},
"skip-silences": {
"description": "Ignorer automatiquement les sections de silence dans les chansons",
@ -821,6 +843,14 @@
"label": "Rend les paroles parfaitement synchronisées",
"tooltip": "Calcul à la milliseconde près l'affichage de la ligne suivante (peut avoir un faible impact sur les performances)"
},
"preferred-provider": {
"label": "Fournisseur privilégié",
"none": {
"label": "Aucun",
"tooltip": "Aucun fournisseur privilégié"
},
"tooltip": "Choisissez le fournisseur par défaut à utiliser"
},
"romanization": {
"label": "Romaniser les paroles",
"tooltip": "Si les paroles sont dans une autre langue, essayez de les afficher dans une version latine."
@ -853,6 +883,27 @@
"description": "Ajoute un widget TouchBar pour les utilisateurs de macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Rend la fenêtre de l'application transparente",
"menu": {
"opacity": {
"label": "Opacité",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Type",
"submenu": {
"acrylic": "Acrylique",
"mica": "Mica",
"none": "Aucun",
"tabbed": "À onglets"
}
}
},
"name": "Lecteur transparent"
},
"tuna-obs": {
"description": "Intégration avec le plugin OBS Tuna",
"name": "Tuna OBS"

View File

@ -421,6 +421,13 @@
}
}
},
"custom-output-device": {
"description": "गानों के लिए एक कस्टम आउटपुट मीडिया डिवाइस कॉन्फ़िगर करें",
"menu": {
"device-selector": "डिवाइस चुनें"
},
"name": "अपनी पसंद का आउटपुट डिवाइस"
},
"disable-autoplay": {
"description": "गीत को \"रुके हुए \" मोड में शुरू करता है",
"menu": {

View File

@ -150,6 +150,13 @@
"visual-tweaks": {
"label": "Megjelenési beállítások",
"submenu": {
"custom-window-title": {
"label": "Saját ablak cím",
"prompt": {
"label": "Kérem az egyéni ablak címét: (hagyd üresen a kikapcsoláshoz)",
"placeholder": "Példa: Youtube Music"
}
},
"like-buttons": {
"default": "Alapértelmezett",
"force-show": "Megjelenítés kényszerítése",
@ -333,6 +340,28 @@
"description": "Hang tömörítés alkalmazása (csökkenti a jel legzajosabb részeinek hangerősségét, és emeli a legcsendesebb részek hangerősségét)",
"name": "Hangtömörítő"
},
"auth-proxy-adapter": {
"menu": {
"disable": "Proxy adapder kikapcsolása",
"enable": "Proxy adapter bekapcsolása",
"hostname": {
"label": "Gazdanév"
},
"port": {
"label": "Port"
}
},
"prompt": {
"hostname": {
"label": "Adjon meg egy hosztnevet a lokális proxy szerverhez (újraindítást igényel):",
"title": "Proxy hosztnév"
},
"port": {
"label": "Adjon meg egy portot a lokális proxy szerverhez (újraindátást igényel):",
"title": "Proxy Port"
}
}
},
"blur-nav-bar": {
"description": "Átlátszóvá és elmosódottá teszi a navigációs sávot",
"name": "Navigációs sáv elmosása"
@ -357,6 +386,11 @@
},
"templates": {
"title": "Feliratválasztó megnyitása"
},
"toast": {
"caption-changed": "Felirat {{language}} nyelvűre állítva",
"caption-disabled": "Feliratok kikapcsolva",
"no-captions": "Nincsenek elérhető feliratok ehhez a dalhoz"
}
},
"compact-sidebar": {
@ -385,6 +419,18 @@
}
}
},
"custom-output-device": {
"menu": {
"device-selector": "Eszköz kiválasztása"
},
"name": "Saját kimeneti eszköz",
"prompt": {
"device-selector": {
"label": "Válaszd ki a kimeneti eszközt",
"title": "Kimeneti eszköz választása"
}
}
},
"disable-autoplay": {
"description": "Ez a funkció kikapcsolja az automatikus lejátszást, így a zenék nem indulnak el maguktól. Amikor egy album vagy egy dal lejátszása véget ér, a következő szám nem kezdődik el automatikusan. A bővítmény használata során minden zenét manuálisan kell elindítani",
"menu": {
@ -408,7 +454,15 @@
"hide-duration-left": "Hátralévő idő elrejtése",
"hide-github-button": "GitHub url gombjának elrejtése",
"play-on-youtube-music": "Lejátszás a YouTube Music-on",
"set-inactivity-timeout": "Inaktivitási időkorlát beállítása"
"set-inactivity-timeout": "Inaktivitási időkorlát beállítása",
"set-status-display-type": {
"label": "Tevékenység szöveg",
"submenu": {
"artist": "Hallgatja: {artist}",
"title": "Hallgatja: {song title}",
"youtube-music": "Hallgatja: YouTube Music"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
@ -576,7 +630,15 @@
},
"navigation": {
"description": "Következő/Vissza navigációs nyilak közvetlenül az interfészbe integrálva, mint a kedvenc böngésződben",
"name": "Navigáció"
"name": "Navigáció",
"templates": {
"back": {
"title": "Előző oldal"
},
"forward": {
"title": "Következő oldal"
}
}
},
"no-google-login": {
"description": "A Bejelentkezés gomb eltávolítása az interfészről (Jobb fentről eltünik a bejelentkezés gomb.)",
@ -600,6 +662,10 @@
},
"name": "Értesítések"
},
"performance-improvement": {
"description": "Teljesítmény fejlesztése kísérleti kódok engedélyezésével",
"name": "Teljesítmény fejlesztése [Béta]"
},
"picture-in-picture": {
"description": "Lehetővé teszi az alkalmazás kép a képben módra váltását",
"menu": {

View File

@ -150,6 +150,13 @@
"visual-tweaks": {
"label": "Penyesuaian Visual",
"submenu": {
"custom-window-title": {
"label": "Judul jendela kustom",
"prompt": {
"label": "Masukkan judul jendela kustom (kosongkan untuk menonaktifkan)",
"placeholder": "Contoh: YouTube Music"
}
},
"like-buttons": {
"default": "Standar",
"force-show": "Pertunjukan paksa",
@ -414,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Atur perangkat media keluaran khusus untuk lagu-lagu",
"menu": {
"device-selector": "Pilih Perangkat"
},
"name": "Perangkat Keluaran Kustom",
"prompt": {
"device-selector": {
"label": "Pilih perangkat media keluaran yang akan digunakan",
"title": "Pilih Perangkat Keluaran"
}
}
},
"disable-autoplay": {
"description": "Buat lagu mulai dalam mode \"jeda\"",
"menu": {
@ -437,7 +457,15 @@
"hide-duration-left": "Sembunyikan sisa durasi",
"hide-github-button": "Sembunyikan tombol link GitHub",
"play-on-youtube-music": "Mainkan di YouTube Music",
"set-inactivity-timeout": "Tetapkan batas waktu tidak aktif"
"set-inactivity-timeout": "Tetapkan batas waktu tidak aktif",
"set-status-display-type": {
"label": "Teks status",
"submenu": {
"artist": "Sedang mendengarkan {artist}",
"title": "Sedang mendengarkan {song title}",
"youtube-music": "Sedang mendengarkan YouTube Music"
}
}
},
"name": "Rich Presence Discord",
"prompt": {
@ -454,16 +482,16 @@
"buttons": {
"ok": "Oke"
},
"message": "Argh! Maaf, dowloadnya gagal…",
"title": "Downloadnya error!"
"message": "Argh! Maaf, dowload gagal…",
"title": "Terjadi kesalahan dalam unduhan!"
},
"start-download-playlist": {
"buttons": {
"ok": "Oke"
},
"detail": "({{playlistSize}} lagu-lagu)",
"message": "Mengunduh Playlist {{playlistTitle}}",
"title": "Download dimulai"
"message": "Mengunduh Daftar Putar {{playlistTitle}}",
"title": "Pengunduhan dimulai"
}
},
"feedback": {
@ -474,8 +502,8 @@
"download-progress": "Mengunduh: {{percent}}%",
"downloading": "Mengunduh…",
"downloading-counter": "Mengunduh {{current}}/{{total}}…",
"downloading-playlist": "Mengunduh playlist \"{{playlistTitle}}\" - {{playlistSize}} lagu ({{playlistId}})",
"error-while-downloading": "Gagal mengunduh \"{{author}} - {{title}}\": {{error}}",
"downloading-playlist": "Mengunduh Daftar Putar \"{{playlistTitle}}\" - {{playlistSize}} lagu-lagu ({{playlistId}})",
"error-while-downloading": "Gagal dalam mengunduh \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Folder {{playlistFolder}} sudah ada",
"getting-playlist-info": "Mendapatkan informasi playlist…",
"loading": "Memuat…",
@ -530,7 +558,7 @@
}
}
},
"name": "Equalizer"
"name": "Ekualiser"
},
"exponential-volume": {
"description": "Buat penggeser volume menjadi eksponen sehingga memudahkan memilih volume yang lebih rendah.",
@ -729,6 +757,7 @@
"listenbrainz": {
"token": "Masukkan token pengguna ListenBrainz"
},
"scrobble-alternative-artist": "Pindah ke artis lain",
"scrobble-alternative-title": "Gunakan judul alternatif",
"scrobble-other-media": "Scrobble media lain"
},
@ -814,6 +843,14 @@
"label": "Buat liriknya tersinkronisasi dengan sempurna",
"tooltip": "Hitung hingga milidetik tampilan baris berikutnya (dapat berdampak kecil pada kinerja)"
},
"preferred-provider": {
"label": "Penyedia Pilihan",
"none": {
"label": "Tidak ada",
"tooltip": "Tidak ada penyedia pilihan"
},
"tooltip": "Pilih penyedia bawaan untuk dipakai"
},
"romanization": {
"label": "Romanize Liriknya",
"tooltip": "Apabila lirik berada dalam bahasa berbeda, cobalah untuk menampilkan versi latinnya."
@ -846,6 +883,27 @@
"description": "Tambahkan widget TouchBar untuk pengguna macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Buat jendela aplikasi transparan",
"menu": {
"opacity": {
"label": "Opasitas",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tipe",
"submenu": {
"acrylic": "Akrilik",
"mica": "Mika",
"none": "TIdak ada",
"tabbed": "Tabulasi"
}
}
},
"name": "Pemutar Transparan"
},
"tuna-obs": {
"description": "Integrasi dengan plugin Tuna OBS",
"name": "Tuna OBS"

View File

@ -150,6 +150,13 @@
"visual-tweaks": {
"label": "Miglioramenti visivi",
"submenu": {
"custom-window-title": {
"label": "Personalizza titolo finestra",
"prompt": {
"label": "Inserisci un titolo della finestra personalizzato: (lascia vuoto per disattivare)",
"placeholder": "Esempio: YouTube Music"
}
},
"like-buttons": {
"default": "Predefinito",
"force-show": "Forza la visualizzazione",
@ -414,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Scegli da quale uscita audio vuoi riprodurre i brani",
"menu": {
"device-selector": "Seleziona un'uscita"
},
"name": "Dispositivo di Output Personalizzato",
"prompt": {
"device-selector": {
"label": "Scegli il dispositivo d'output da utilizzare",
"title": "Scegli il dispositivo d'output"
}
}
},
"disable-autoplay": {
"description": "Fa iniziare i brani in modalità \"pausa\"",
"menu": {
@ -437,7 +457,15 @@
"hide-duration-left": "Nascondi la durata rimasta",
"hide-github-button": "Nascondi il pulsante link a GitHub",
"play-on-youtube-music": "Riproduci su YouTube Music",
"set-inactivity-timeout": "Imposta il timeout di inattività"
"set-inactivity-timeout": "Imposta il timeout di inattività",
"set-status-display-type": {
"label": "Testo dello status",
"submenu": {
"artist": "Stai ascoltando {artist}",
"title": "Stai ascoltando {song title}",
"youtube-music": "Ascoltando YouTube Music"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
@ -729,6 +757,7 @@
"listenbrainz": {
"token": "Inserire il token utente per ListenBrainz"
},
"scrobble-alternative-artist": "Usa artisti alternativi",
"scrobble-alternative-title": "Usa titoli alternativi",
"scrobble-other-media": "Scrobble altri media"
},
@ -814,6 +843,14 @@
"label": "Rendi i testi perfettamente sincronizzati",
"tooltip": "Calcola al millisecondo la visualizzazione della riga successiva (può avere un piccolo impatto sulle prestazioni)"
},
"preferred-provider": {
"label": "Provider preferito",
"none": {
"label": "Nessuno",
"tooltip": "Nessun provider preferito"
},
"tooltip": "Scegli quale provider predefinito utilizzare"
},
"romanization": {
"label": "Testi in caratteri occidentali",
"tooltip": "Qualora il testo fosse scritto in una lingua non occidentale, prova a visualizzarlo in caratteri latini."
@ -846,6 +883,27 @@
"description": "Aggiunge un widget TouchBar per gli utenti macOS",
"name": "Touch Bar (per MacOS)"
},
"transparent-player": {
"description": "Rende trasparente la finestra del programma",
"menu": {
"opacity": {
"label": "Opacità",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tipo",
"submenu": {
"acrylic": "Acrilico",
"mica": "Mica",
"none": "Nessuno",
"tabbed": "In scheda"
}
}
},
"name": "Player Trasparente"
},
"tuna-obs": {
"description": "Integrazione con il plugin OBS Tuna",
"name": "Tuna OBS"

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "曲用のカスタム出力メディアデバイスを構成する",
"menu": {
"device-selector": "デバイスの選択"
},
"name": "カスタム出力デバイス",
"prompt": {
"device-selector": {
"label": "使用する出力メディアデバイスを選択します",
"title": "出力デバイスの選択"
}
}
},
"disable-autoplay": {
"description": "曲を「一時停止」モードで始めさせます",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "残りの再生時間を隠す",
"hide-github-button": "GitHubリンクボタンを隠す",
"play-on-youtube-music": "YouTube Musicで再生",
"set-inactivity-timeout": "タイムアウト時間を設定"
"set-inactivity-timeout": "タイムアウト時間を設定",
"set-status-display-type": {
"label": "ステータステキスト",
"submenu": {
"artist": "{artist}を聴いている",
"title": "{曲名}を聴いている",
"youtube-music": "YouTube Musicを聴く"
}
}
},
"name": "Discordアクティビティステータス",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "ListenBrainzユーザートークンを入力してください"
},
"scrobble-alternative-artist": "代替アーティストを使用する",
"scrobble-alternative-title": "代替タイトルを使用する",
"scrobble-other-media": "他のメディアをScrobbleする"
},
@ -821,6 +843,14 @@
"label": "歌詞を完璧に同期させる",
"tooltip": "次の行の表示をミリ秒単位で計算する(パフォーマンスに若干の影響を与える可能性があります)"
},
"preferred-provider": {
"label": "優先プロバイダー",
"none": {
"label": "なし",
"tooltip": "指定医療機関なし"
},
"tooltip": "使用するデフォルトプロバイダを選択してください"
},
"romanization": {
"label": "ローマ字歌詞",
"tooltip": "歌詞が異なる言語で書かれている場合は、ラテン語バージョンを表示するようにしてください。"
@ -853,6 +883,27 @@
"description": "masOSユーザー向けにTouchBarウィジェットを追加",
"name": "TouchBar"
},
"transparent-player": {
"description": "アプリウィンドウを透明にする",
"menu": {
"opacity": {
"label": "不透明度",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "タイプ",
"submenu": {
"acrylic": "アクリル",
"mica": "マイカ(Mica)",
"none": "なし",
"tabbed": "タブ付き"
}
}
},
"name": "透明プレイヤー"
},
"tuna-obs": {
"description": "OBSのプラグインTunaの統合",
"name": "Tuna OBS"

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "미디어 출력 장치 구성",
"menu": {
"device-selector": "장치 선택"
},
"name": "출력 장치 커스텀",
"prompt": {
"device-selector": {
"label": "사용할 미디어 출력 장치를 선택하세요",
"title": "출력 장치 선택"
}
}
},
"disable-autoplay": {
"description": "노래를 '일시 정지' 모드로 시작하게 합니다",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "남은 재생 시간 숨기기",
"hide-github-button": "GitHub 링크 버튼 숨기기",
"play-on-youtube-music": "유튜브 뮤직에서 재생",
"set-inactivity-timeout": "비활성 시간 제한 설정"
"set-inactivity-timeout": "비활성 시간 제한 설정",
"set-status-display-type": {
"label": "상태 텍스트",
"submenu": {
"artist": "{아티스트} 듣는 중",
"title": "{곡 제목} 듣는 중",
"youtube-music": "YouTube Music 듣는 중"
}
}
},
"name": "디스코드 활동 상태",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "ListenBrainz 유저 토큰 입력"
},
"scrobble-alternative-artist": "대체 아티스트 명 사용",
"scrobble-alternative-title": "대체 제목 사용하기",
"scrobble-other-media": "다른 미디어 스크로블하기"
},
@ -821,6 +843,14 @@
"label": "가사를 최대한 정교하게 동기화",
"tooltip": "다음 줄의 표시를 밀리초 단위로 계산합니다 (성능에 약간의 영향을 미칠 수 있음)"
},
"preferred-provider": {
"label": "선호하는 가사 제공자",
"none": {
"label": "없음",
"tooltip": "선호하는 가사 제공자 없음"
},
"tooltip": "사용할 기본 가사 제공자를 선택하세요"
},
"romanization": {
"label": "가사 로마자 변환",
"tooltip": "가사가 영어가 아닌 언어로 되어있는 경우, 로마자 표기를 표시합니다."
@ -853,6 +883,27 @@
"description": "macOS 사용자를 위한 TouchBar 위젯을 추가합니다",
"name": "TouchBar"
},
"transparent-player": {
"description": "애플리케이션 창을 투명하게 만듭니다",
"menu": {
"opacity": {
"label": "불투명도",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "종류",
"submenu": {
"acrylic": "아크릴",
"mica": "미카",
"none": "없음",
"tabbed": "탭"
}
}
},
"name": "투명 플레이어"
},
"tuna-obs": {
"description": "OBS의 확장인 Tuna와의 통합을 활성화합니다",
"name": "Tuna OBS"

View File

@ -59,6 +59,7 @@
},
"unresponsive": {
"buttons": {
"quit": "ഉപേക്ഷിക്കുക",
"relaunch": "പുനരാരംഭിക്കുക",
"wait": "കാത്തിരിക്കൂ"
},
@ -109,5 +110,22 @@
"enabled": "പ്രവർത്തനക്ഷമമാക്കി"
}
}
},
"plugins": {
"video-toggle": {
"menu": {
"align": {
"label": "ക്രമപ്പെടുത്തൽ",
"submenu": {
"left": "ഇടത്",
"middle": "മധ്യഭാഗം",
"right": "വലത്"
}
}
},
"templates": {
"button-video": "വീഡിയോ"
}
}
}
}

View File

@ -124,7 +124,7 @@
"language": {
"dialog": {
"message": "Bahasa akan ditukar selepas dimulakan semula",
"title": "Bahasa Berubah"
"title": "Bahasa diubah"
},
"label": "Bahasa",
"submenu": {
@ -150,11 +150,18 @@
"visual-tweaks": {
"label": "Pembaikan Visual",
"submenu": {
"custom-window-title": {
"label": "Tajuk tetingkap tersuai",
"prompt": {
"label": "Masukkan tajuk tetingkap tersuai: (biarkan kosong untuk matikan)",
"placeholder": "Contoh: YouTube Music"
}
},
"like-buttons": {
"default": "Lalai",
"force-show": "Paksa pamer",
"hide": "Sembunyi",
"label": "Suka butang"
"label": "Butang suka"
},
"remove-upgrade-button": "Buang butang naik taraf",
"theme": {
@ -243,8 +250,17 @@
"pixels": "{{blurAmount}}piksel"
}
},
"buffer": {
"label": "Buffer",
"submenu": {
"buffer": "{{buffer}}"
}
},
"opacity": {
"label": "Kelegapan"
"label": "Kelegapan",
"submenu": {
"percent": "{{opacity}}%"
}
},
"quality": {
"label": "Kualiti",
@ -271,7 +287,11 @@
"name": "Mod Sekitaran"
},
"amuse": {
"description": "Menambahkan sokongan YouTube Music untuk widget sedang dimain Amuse oleh 6K Labs"
"description": "Menambahkan sokongan YouTube Music untuk widget sedang dimain Amuse oleh 6K Labs",
"name": "Terhibur",
"response": {
"query": "API server Amuse telah berjalan. GET /query untuk mendapatkan maklumat lagu."
}
},
"api-server": {
"description": "Perlukan server API untuk mengawal pemain",
@ -321,13 +341,31 @@
"name": "Pemampat Audio"
},
"auth-proxy-adapter": {
"description": "Sokongan untuk penggunaan perkhidmatan proksi pengesahan",
"menu": {
"disable": "Matikan adapter proksi",
"enable": "Nyalakan adapter proksi",
"hostname": {
"label": "Nama perumah"
},
"port": {
"label": "Port"
}
},
"name": "Adapter Auth Proksi",
"prompt": {
"hostname": {
"label": "Nyatakan nama hos untuk server proksi tempatan (memerlukan mulakan semula):",
"title": "Nama hos proksi"
},
"port": {
"label": "Masukkan port untuk server proksi tempatan (memerlukan restart):",
"title": "Port Proksi"
}
}
},
"blur-nav-bar": {
"description": "Menjadikan bar navigasi telus dan kabur",
"name": "Kaburkan Bar navigasi"
},
"bypass-age-restrictions": {
@ -347,6 +385,172 @@
"none": "Tiada",
"title": "Pilih bahasa kapsyen"
}
},
"templates": {
"title": "Buka pemilih kapsyen"
},
"toast": {
"caption-changed": "Sarikata berubah kepada {{language}}",
"caption-disabled": "Sarikata dimatikan",
"no-captions": "Tiada sarikata untuk lagu ini"
}
},
"compact-sidebar": {
"description": "Sentiasa tetapkan bar sisi dalam mod padat",
"name": "Bar Sisi Padat"
},
"crossfade": {
"description": "Pudar silang antara lagu",
"menu": {
"advanced": "Maju"
},
"name": "Pudar Selang [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Pudar dalam tempoh (ms)",
"fade-out-duration": "Tempoh pudar (ms)",
"fade-scaling": {
"label": "Penskalaan pudar",
"linear": "Linear",
"logarithmic": "Logaritma"
},
"seconds-before-end": "Pudar silang N saat sebelum tamat"
},
"title": "Opsi pudar silang"
}
}
},
"custom-output-device": {
"description": "Konfigurasikan peranti media output tersuai untuk lagu",
"menu": {
"device-selector": "Pilih peranti"
},
"name": "Peranti Output Tersuai",
"prompt": {
"device-selector": {
"label": "Pilih peranti media output yang akan digunakan",
"title": "Pilih Peranti Output"
}
}
},
"disable-autoplay": {
"description": "Membuat lagu bermula dalam mod \"jeda\"",
"menu": {
"apply-once": "Terpakai hanya pada permulaan"
},
"name": "Matikan automain"
},
"discord": {
"backend": {
"already-connected": "Cuba untuk menyambung dengan sambungan aktif",
"connected": "Disambungkan ke Discord",
"disconnected": "Diputuskan sambungan daripada Discord"
},
"description": "Tunjukkan kepada rakan anda perkara yang anda dengar dengan Rich Presence",
"menu": {
"auto-reconnect": "Auto sambung semula",
"clear-activity": "Padamkan aktiviti",
"clear-activity-after-timeout": "Kosongkan aktiviti selepas tamat masa",
"connected": "Tersambung",
"disconnected": "Tidak disambungkan",
"hide-duration-left": "Sembunyikan tempoh yang tinggal",
"hide-github-button": "Sembunyikan Butang pautan GitHub",
"play-on-youtube-music": "Main di YouTube Music",
"set-inactivity-timeout": "Tetapkan tamat masa tidak aktif",
"set-status-display-type": {
"label": "Teks status",
"submenu": {
"artist": "Sedang mendengar {artist}",
"youtube-music": "Mendengar YouTube Music"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
"set-inactivity-timeout": {
"label": "Masukkan tamat masa tidak aktif dalam beberapa saat:",
"title": "Tetapkan tamat masa tidak aktif"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Argh! Maaf, muat turun gagal…",
"title": "Ralat dalam muat turun!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} lagu)",
"message": "Memuat turun senarai main {{playlistTitle}}",
"title": "Memuat turn bermula"
}
},
"feedback": {
"conversion-progress": "Penukaran: {{percent}}%",
"converting": "Menukarkan…",
"done": "Selesai: {{filePath}}",
"download-info": "Memuat turun {{artist}} - {{title}} [{{videoId}}",
"downloading": "Memuat turun…",
"downloading-counter": "Memuat turun {{current}}/{{total}}…",
"downloading-playlist": "Memuat turun senarai main \"{{playlistTitle}}\" - {{playlistSize}} lagu ({{playlistId}})",
"error-while-downloading": "Gagal memuat turun \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Folder {{playlistFolder}} sudah ada",
"getting-playlist-info": "Mendapatkan maklumat senarai main…",
"loading": "Memuat…",
"playlist-has-only-one-song": "Senarai main hanya mempunyai satu item, memuat turunnya terus",
"playlist-id-not-found": "ID senarai main tidak dijumpai",
"playlist-is-empty": "Senarai main kosong",
"saving": "Menyimpan…",
"trying-to-get-playlist-id": "Mencuba untuk mendapatkan ID senarai main: {{playlistId}}",
"video-id-not-found": "Video tidak dijumpai"
}
},
"description": "Memuat turun audio MP3 / sumber terus dari antara muka",
"menu": {
"choose-download-folder": "Pilih folder muat turun",
"download-finish-settings": {
"prompt": {
"title": "Konfigurasikan masa untuk memuat turun"
},
"submenu": {
"enabled": "Dinyalakan",
"mode": "Mod masa",
"percent": "Peratus",
"seconds": "Saat"
}
},
"download-playlist": "Muat turun senarai main"
}
},
"lumiastream": {
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Menambahkan sokongan lirik untuk kebanyakan lagu",
"menu": {
"romanized-lyrics": "Lirik Romanized"
},
"name": "Lirik Genius",
"renderer": {
"fetched-lyrics": "Lirik yang diambil untuk Genius"
}
},
"music-together": {
"description": "Kongsi senarai main dengan orang lain. Apabila hos memainkan lagu, semua orang akan mendengar lagu yang sama",
"internal": {
"save": "Simpan",
"unknown-user": "Pengguna tidak diketahui"
},
"menu": {
"close": "Tutup Music Together"
}
},
"synced-lyrics": {

View File

@ -189,12 +189,12 @@
"new": "NIEUW"
},
"view": {
"label": "Weergeven",
"label": "Bekijken",
"submenu": {
"force-reload": "Forceer Herladen",
"reload": "Herladen",
"reset-zoom": "Ware Grootte",
"toggle-fullscreen": "Volledig Scherm Wisselen",
"toggle-fullscreen": "Volledig Scherm Ja/Nee",
"zoom-in": "Inzoomen",
"zoom-out": "Uitzoomen"
}
@ -204,12 +204,12 @@
"next": "Volgende",
"play-pause": "Afspelen/Pauze",
"previous": "Vorige",
"quit": "Afsluiten",
"quit": "Verlaat",
"restart": "Herstarten App",
"show": "Weergeven Venster",
"tooltip": {
"default": "YouTube Music",
"with-song-info": "YouTube Music: {{artiest}} - {{titel}}"
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
}
}
},
@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Configureer een aangepast media uitvoerapparaat voor liedjes",
"menu": {
"device-selector": "Selecteer je apparaat"
},
"name": "Aangepast uitvoerapparaat",
"prompt": {
"device-selector": {
"label": "Kies het uitvoermedia-apparaat dat gebruikt zal worden",
"title": "Selecteer het uitvoermedia"
}
}
},
"disable-autoplay": {
"description": "Zorgt ervoor dat nummers starten in 'gepauzeerde' modus",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "Verberg resterende tijd",
"hide-github-button": "GitHub-knop verbergen",
"play-on-youtube-music": "Afspelen op YouTube Music",
"set-inactivity-timeout": "Inactiviteitstime-out instellen"
"set-inactivity-timeout": "Inactiviteitstime-out instellen",
"set-status-display-type": {
"label": "Status tekst",
"submenu": {
"artist": "Naar {artist} aan het luisteren",
"title": "Naar {song title} aan het luisteren",
"youtube-music": "Naar Youtube Music aan het luisteren"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "Voer het ListenBrainz-gebruikerstoken in"
},
"scrobble-alternative-artist": "Gebruik alternatieve artiesten",
"scrobble-alternative-title": "Gebruik alternatieve titels",
"scrobble-other-media": "Scrobble andere media"
},
@ -821,6 +843,14 @@
"label": "Zorg ervoor dat de songteksten perfect gesynchroniseerd zijn",
"tooltip": "Bereken tot op de milliseconde de weergave van de volgende regel (kan een kleine impact hebben op de prestaties)"
},
"preferred-provider": {
"label": "Voorkeur van Provider",
"none": {
"label": "Geen",
"tooltip": "Geen provider beschikbaar"
},
"tooltip": "Kies de standaardprovider om te gebruiken"
},
"romanization": {
"label": "Romaniseer songtekst",
"tooltip": "Als de songtekst in een andere taal is, probeer dan een Latijnse versie weer te geven."
@ -853,6 +883,24 @@
"description": "Voegt een TouchBar-widget toe voor macOS-gebruikers",
"name": "TouchBar"
},
"transparent-player": {
"menu": {
"opacity": {
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Soort",
"submenu": {
"acrylic": "Acrylic",
"mica": "Mica",
"none": "Geen",
"tabbed": "Tabbed"
}
}
}
},
"tuna-obs": {
"description": "Integratie met OBS's plug-in Tuna",
"name": "Tuna OBS"

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Skonfiguruj niestandardowe wyjście audio dla odtwarzanych utworów",
"menu": {
"device-selector": "Wybierz urządzenie"
},
"name": "Niestandardowe wyjście audio",
"prompt": {
"device-selector": {
"label": "Wybierz wyjście audio które ma być użyte",
"title": "Wybierz wyjście audio"
}
}
},
"disable-autoplay": {
"description": "Wyłącza automatyczne odtwarzanie utworów",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "Ukryj pozostały czas trwania",
"hide-github-button": "Ukryj przycisk do GitHub",
"play-on-youtube-music": "Odtwórz w YouTube Music",
"set-inactivity-timeout": "Ustaw limit czasu bezczynności"
"set-inactivity-timeout": "Ustaw limit czasu bezczynności",
"set-status-display-type": {
"label": "Opis statusu",
"submenu": {
"artist": "Słucha {artist}",
"title": "Słucha {song title}",
"youtube-music": "Słucha YouTube Music"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "Podaj token użytkownika ListenBrainz"
},
"scrobble-alternative-artist": "Urzyj alternatywnych artytów",
"scrobble-alternative-title": "Użyj alternatywnych tytułów",
"scrobble-other-media": "Scrobbluj pozostałe multimedia"
},
@ -821,6 +843,14 @@
"label": "Zsynchronizuj tekst utworu do perfekcji",
"tooltip": "Wylicz czas wyświetlania następnej linijki co do milisekundy (może mieć mały wpływ na wydajność systemu)"
},
"preferred-provider": {
"label": "Preferowane Źródło",
"none": {
"label": "Żaden",
"tooltip": "Brak preferowanego źródła"
},
"tooltip": "Wybierz domyślne źródło"
},
"romanization": {
"label": "Romanizacja utworów",
"tooltip": "Jeżeli tekst piosenki nie jest w alfabecie łacińskim, poddaje ją romanizacji, czyli przedstawia mowy za pomocą owych liter."
@ -853,6 +883,27 @@
"description": "Dodaje widżet do paska dotykowego dla użytkowników systemu macOS",
"name": "Pasek dotykowy"
},
"transparent-player": {
"description": "Sprawia, że okno aplikacji jest przeźroczyste",
"menu": {
"opacity": {
"label": "Nieprzezroczystość",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Typ",
"submenu": {
"acrylic": "Akryl",
"mica": "Mika",
"none": "Brak",
"tabbed": "Zakładkowy"
}
}
},
"name": "Przeźroczysty odtwarzacz"
},
"tuna-obs": {
"description": "Integracja z wtyczką OBS Tuna",
"name": "Tuna OBS"

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Configure um dispositivo de saída de mídia personalizado para músicas",
"menu": {
"device-selector": "Selecionar dispositivo"
},
"name": "Dispositivo de saída personalizado",
"prompt": {
"device-selector": {
"label": "Escolha o dispositivo de saída de mídia que será usado",
"title": "Selecionar dispositivo de saída"
}
}
},
"disable-autoplay": {
"description": "Faz a música começar no modo \"pausado\"",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "Ocultar duração restante",
"hide-github-button": "Ocultar botão do GitHub",
"play-on-youtube-music": "Reproduzir no YouTube Music",
"set-inactivity-timeout": "Definir tempo limite de inatividade"
"set-inactivity-timeout": "Definir tempo limite de inatividade",
"set-status-display-type": {
"label": "Texto de status",
"submenu": {
"artist": "Ouvindo {artist}",
"title": "Ouvindo {song title}",
"youtube-music": "Ouvindo YouTube Music"
}
}
},
"name": "Rich Presence do Discord",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "Insira o token de usuário ListenBrainz"
},
"scrobble-alternative-artist": "Use artistas alternativos",
"scrobble-alternative-title": "Usar títulos alternativos",
"scrobble-other-media": "Scrobble outras mídias"
},
@ -821,6 +843,14 @@
"label": "Deixa as letras perfeitamente sincronizadas",
"tooltip": "Calcular até o milissegundo a exibição da próxima linha (pode ter um pequeno impacto no desempenho)"
},
"preferred-provider": {
"label": "Provedor Preferido",
"none": {
"label": "Nenhum",
"tooltip": "Sem provedor preferido"
},
"tooltip": "Escolha o provedor padrão para uso"
},
"romanization": {
"label": "Letras romanizadas",
"tooltip": "Se as letras estiverem em um idioma diferente, tente exibir uma versão latina."
@ -853,6 +883,27 @@
"description": "Adiciona um widget TouchBar para usuários do macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Faz a janela do app transparente",
"menu": {
"opacity": {
"label": "Opacidade",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tipo",
"submenu": {
"acrylic": "Acrílico",
"mica": "Mica",
"none": "Nenhum",
"tabbed": "Em abas"
}
}
},
"name": "Player transparente"
},
"tuna-obs": {
"description": "Integração com o plugin Tuna do OBS",
"name": "Tuna OBS"

View File

@ -136,13 +136,13 @@
"start-at-login": "Iniciar com o sistema",
"starting-page": {
"label": "Página inicial",
"unset": "Limpar"
"unset": "Indefinida"
},
"tray": {
"label": "Área de notificação",
"submenu": {
"disabled": "Desativada",
"enabled-and-hide-app": "Ativado e ocultar aplicação",
"enabled-and-hide-app": "Ativada e ocultar aplicação",
"enabled-and-show-app": "Ativada e a mostrar aplicação",
"play-pause-on-click": "Reprodução/Pausa ao clicar"
}
@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Configurar um dispositivo de saída padrão para as músicas",
"menu": {
"device-selector": "Selecionar dispositivo"
},
"name": "Dispositivo personalizado",
"prompt": {
"device-selector": {
"label": "Escolha o dispositivo de saída a utilizar",
"title": "Selecione o dispositivo de saída"
}
}
},
"disable-autoplay": {
"description": "Faz com que a música inicie no modo \"pausa\"",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "Ocultar tempo restante",
"hide-github-button": "Ocultar botão GitHub",
"play-on-youtube-music": "Reproduzir em YouTube Music",
"set-inactivity-timeout": "Definir tempo de inatividade"
"set-inactivity-timeout": "Definir tempo de inatividade",
"set-status-display-type": {
"label": "Texto de estado",
"submenu": {
"artist": "A ouvir {artist}",
"title": "A ouvir {song title}",
"youtube-music": "A reproduzir YouTube Music"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "Introduza o 'token' do utilizador ListenBrainz"
},
"scrobble-alternative-artist": "Utilizar artistas alternativos",
"scrobble-alternative-title": "Utilizar títulos alternativos",
"scrobble-other-media": "Scrobble de outros conteúdos"
},
@ -821,6 +843,14 @@
"label": "Sincronização perfeita da entre letra e música",
"tooltip": "Calcular, ao milissegundo, a exibição da linha seguinte (pode ter um pequeno impacto no desempenho)"
},
"preferred-provider": {
"label": "Serviço preferencial",
"none": {
"label": "Nenhum",
"tooltip": "Nenhum serviço preferencial"
},
"tooltip": "Escolha o serviço padrão a utilizar"
},
"romanization": {
"label": "Letras romanas",
"tooltip": "Se as letras estiverem num idioma diferente, tentar mostrar uma versão em latim"
@ -853,6 +883,27 @@
"description": "Adicionar widget TouchBar para utilizadores macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Tornar janela da aplicação transparente",
"menu": {
"opacity": {
"label": "Opacidade",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tipo",
"submenu": {
"acrylic": "Acrílico",
"mica": "Mica",
"none": "Nada",
"tabbed": "Separadores"
}
}
},
"name": "Reprodutor transparente"
},
"tuna-obs": {
"description": "Integração com o plugin Tuna do OBS",
"name": "Tuna OBS"

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Configurați un dispozitiv de ieșire personalizat pentru melodii",
"menu": {
"device-selector": "Selectați dispozitivul"
},
"name": "Dispozitiv de ieșire personalizat",
"prompt": {
"device-selector": {
"label": "Alegeți dispozitivul media de ieșire care va fi utilizat",
"title": "Selectați dispozitivul de ieșire"
}
}
},
"disable-autoplay": {
"description": "Face cântecul să înceapă în modul \"pauză\"",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "Ascunde timpul rămas",
"hide-github-button": "Ascunde butonul cu link-ul GitHub",
"play-on-youtube-music": "Redă pe YouTube Music",
"set-inactivity-timeout": "Setează intervalul de inactivitate"
"set-inactivity-timeout": "Setează intervalul de inactivitate",
"set-status-display-type": {
"label": "Text stare",
"submenu": {
"artist": "Ascultând {artist}",
"title": "Ascultând {song title}",
"youtube-music": "Ascultând YouTube Music"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
@ -712,7 +733,12 @@
}
},
"description": "Permite schimbarea calității video cu un buton prezent peste video",
"name": "Schimbător de calitate video"
"name": "Schimbător de calitate video",
"renderer": {
"quality-settings-button": {
"label": "Deschide setările de calitate"
}
}
},
"scrobbler": {
"description": "Adaugă asistenta pentru scrobbling (etc. last.fm, Listenbrainz)",
@ -731,6 +757,7 @@
"listenbrainz": {
"token": "Introdu token-ul de utilizator ListenBrainz"
},
"scrobble-alternative-artist": "Utilizați artiști alternativi",
"scrobble-alternative-title": "Folosește titluri alternative",
"scrobble-other-media": "Scrobble alte surse media"
},
@ -816,6 +843,14 @@
"label": "Sincronizează versurile perfect",
"tooltip": "Calculează afisarea următoarei linii până la milisecundă (poate afecta performanța)"
},
"preferred-provider": {
"label": "Furnizor preferat",
"none": {
"label": "Niciunul",
"tooltip": "Niciun furnizor preferat"
},
"tooltip": "Alegeți furnizorul preferat"
},
"romanization": {
"label": "Transcrie versurile în alfabet latin",
"tooltip": "Dacă versurile sunt într-o altă limbă, încearcă să afișezi o versiune în alfabet latin."
@ -848,6 +883,27 @@
"description": "Adaugă un widget TouchBar pentru utilizatorii macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Face fereastra aplicației transparentă",
"menu": {
"opacity": {
"label": "Opacitate",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tip",
"submenu": {
"acrylic": "Acrilic",
"mica": "Efect mica",
"none": "Niciunul",
"tabbed": "Cu file"
}
}
},
"name": "Player transparent"
},
"tuna-obs": {
"description": "Integrare cu plugin-ul OBS Tuna",
"name": "Tuna OBS"

View File

@ -53,9 +53,9 @@
"later": "Позже",
"restart-now": "Перезапустить сейчас"
},
"detail": "Перезагрузите приложение для включения плагина {{pluginName}}",
"message": "{{pluginName}} нуждается в перезагрузке",
"title": "Нужна перезагрузка"
"detail": "Перезапустите приложение для включения плагина {{pluginName}}",
"message": "Перезапуск для применения плагина {{pluginName}}",
"title": "Нужен перезапуск"
},
"unresponsive": {
"buttons": {
@ -191,8 +191,8 @@
"view": {
"label": "Вид",
"submenu": {
"force-reload": "Принудительная перезагрузка",
"reload": "Перезагрузить",
"force-reload": "Принудительный перезапуск",
"reload": "Перезапустить",
"reset-zoom": "Текущий размер",
"toggle-fullscreen": "Включить полноэкранный режим",
"zoom-in": "Приблизить",
@ -205,7 +205,7 @@
"play-pause": "Пауза/Продолжить",
"previous": "Предыдущий",
"quit": "Выйти",
"restart": "Перезагрузить приложение",
"restart": "Перезапустить приложение",
"show": "Показать окно",
"tooltip": {
"default": "YouTube Music",
@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Настройка устройства вывода медиа для песен",
"menu": {
"device-selector": "Выберите устройство"
},
"name": "Пользовательское устройство вывода",
"prompt": {
"device-selector": {
"label": "Выберите устройство вывода медиа, которое будет использоваться",
"title": "Выберите устройство вывода"
}
}
},
"disable-autoplay": {
"description": "Запускает песню сразу на паузе",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "Скрыть сколько осталось времени",
"hide-github-button": "Скрыть ссылку на GitHub",
"play-on-youtube-music": "Воспроизвести на YouTube Music",
"set-inactivity-timeout": "Поставить таймер неактивности"
"set-inactivity-timeout": "Поставить таймер неактивности",
"set-status-display-type": {
"label": "Текст статуса",
"submenu": {
"artist": "Слушает {исполнитель}",
"title": "Слушает {название трека}",
"youtube-music": "Слушает YouTube Music"
}
}
},
"name": "Discord Rich Presence",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "Введите токен пользователя ListenBrainz"
},
"scrobble-alternative-artist": "Использовать альтернативных исполнителей",
"scrobble-alternative-title": "Использовать альтернативные названия",
"scrobble-other-media": "Скробблинг других медиа"
},
@ -821,6 +843,14 @@
"label": "Идеально синхронизировать слова",
"tooltip": "До миллисекунды рассчитывает отображение следующей строки(может оказать небольшое влияние на производительность)"
},
"preferred-provider": {
"label": "Предпочитаемый источник",
"none": {
"label": "Никакой",
"tooltip": "Нет предпочитаемого источника"
},
"tooltip": "Выберите источник по умолчанию"
},
"romanization": {
"label": "Романизировать слова",
"tooltip": "Если слова на другом языке, пытаться отображать версию на латинице."
@ -853,6 +883,27 @@
"description": "Добавляет виджет тачбара для пользователей macOS",
"name": "Тачбар"
},
"transparent-player": {
"description": "Делает окно приложения прозрачным",
"menu": {
"opacity": {
"label": "Непрозрачность",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Тип",
"submenu": {
"acrylic": "Акриловый",
"mica": "Слюдяной",
"none": "Отключено",
"tabbed": "Страничный"
}
}
},
"name": "Прозрачный плеер"
},
"tuna-obs": {
"description": "Интеграция с плагином Tuna от OBS",
"name": "Tuna OBS"

214
src/i18n/resources/sk.json Normal file
View File

@ -0,0 +1,214 @@
{
"common": {
"console": {
"plugins": {
"execute-failed": "Nepodarilo sa spustiť plugin {{pluginName}}:{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} spustený za {{ms}}ms",
"initialize-failed": "Zlyhalo spustenie \"{{pluginName}}\" pluginu",
"load-all": "Načítavanie všetkých pluginov",
"load-failed": "Načítanie \"{{pluginName}}\" pluginu zlyhalo",
"loaded": "Plugin \"{{pluginName}}\" sa načítal",
"unload-failed": "Zlyhalo vypnutie \"{{pluginName}}\" pluginu",
"unloaded": "Plugin \"{{pluginName}}\" bol vypnutý"
}
}
},
"language": {
"code": "sk",
"local-name": "Slovenčina",
"name": "Slovak"
},
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Načítanie dokončené. DevTools otvorené"
},
"i18n": {
"loaded": "i18n načítaný"
},
"second-instance": {
"receive-command": "Prijatý príkaz skrze protokol \"{{command}}\""
},
"theme": {
"css-file-not-found": "CSS súbor \"{{cssFile}}\" neexistuje, ignorované"
},
"unresponsive": {
"details": "Aplikácia nereaguje\n{{error}}"
},
"when-ready": {
"clearing-cache-after-20s": "Čistenie medzipamäte aplikácie"
},
"window": {
"tried-to-render-offscreen": "Okno sa pokúšalo vykresliť na pozadí, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
}
},
"dialog": {
"hide-menu-enabled": {
"detail": "Menu je skryté, stlačte \"Alt\" na zobrazenie (alebo \"Escape\" keď používate Aplikačné menu)",
"message": "Skryté menu je zapnuté",
"title": "Skryté menu zapnuté"
},
"need-to-restart": {
"buttons": {
"later": "Neskôr",
"restart-now": "Reštartovať teraz"
},
"detail": "\"{{pluginName}}\" plugin potrebuje reštart aby sa spustil",
"message": "\"{{pluginName}}\" sa potrebuje reštartovať",
"title": "Reštart potrebný"
},
"unresponsive": {
"buttons": {
"quit": "Skončiť",
"relaunch": "Spustiť znova",
"wait": "Počkajte"
},
"detail": "Ospravedlňujeme sa za nepríjemnosti! prosím vyberte, čo robiť:",
"message": "Aplikácia nereaguje",
"title": "Okno nereaguje"
},
"update-available": {
"buttons": {
"disable": "Vypnúť aktualizácie",
"download": "Sťiahnuť",
"ok": "OK"
},
"detail": "Nová verzia je k dispozícii a je možné ju stiahnuť na {{downloadLink}}",
"message": "Nová verzia je k dispozícii",
"title": "Aktualizácia je k dispozícii"
}
},
"menu": {
"about": "O nás",
"navigation": {
"label": "Navigovať",
"submenu": {
"copy-current-url": "Skopírovať aktuálnu URL",
"go-back": "Späť",
"go-forward": "Dopredu",
"quit": "Skončiť",
"restart": "Reštartovať Aplikáciu"
}
},
"options": {
"label": "Možnosti",
"submenu": {
"advanced-options": {
"label": "Rozšírené možnosti",
"submenu": {
"auto-reset-app-cache": "Vymazať vyrovnávaciu pamäť, pri štarte",
"disable-hardware-acceleration": "Vypnúť hardvérovú akceleráciu",
"edit-config-json": "Upraviť config.json",
"override-user-agent": "Prepísať User-Agent",
"restart-on-config-changes": "Reštartovať pri zmene configu",
"set-proxy": {
"label": "Nastavtiť proxy",
"prompt": {
"label": "Zadajte Proxy adresu: (nechajte prázdne pre vypnutie)",
"placeholder": "Príklad: SOCKS5://127.0.0.1:9999",
"title": "Nastavtiť proxy"
}
},
"toggle-dev-tools": "Prepnúť DevTools"
}
},
"always-on-top": "Vždy na vrchu",
"auto-update": "Automatická aktualizácia",
"hide-menu": {
"dialog": {
"message": "Menu bude skryté pri ďalšom štarte, použite [Alt] na ukázanie (alebo [`] ak používate aplikačné menu)",
"title": "Skryté menu Zapnuté"
},
"label": "Skryť menu"
},
"language": {
"dialog": {
"message": "Jazyk sa zmení po reštarte",
"title": "Jazyk sa zmenil"
},
"label": "Jazyk",
"submenu": {
"to-help-translate": "Chcete pomôcť preložiť? Kliknite sem"
}
},
"resume-on-start": "Pokračovať od poslednej piesne, pri spustení aplikácie",
"single-instance-lock": "Zámok jedného spustenia",
"start-at-login": "Spustiť pri štarte",
"starting-page": {
"label": "Spúšťacia stránka",
"unset": "Nevybrať"
},
"tray": {
"label": "Panel oznámení",
"submenu": {
"disabled": "Vypnuý",
"enabled-and-hide-app": "Zapnutý , a skryť okno aplikácie",
"enabled-and-show-app": "Spustené a ukáž aplikáciu",
"play-pause-on-click": "Prehrať/Pozastaviť na stlačenie"
}
},
"visual-tweaks": {
"label": "Vizuálne úpravy",
"submenu": {
"custom-window-title": {
"label": "Vlastný názov okna",
"prompt": {
"label": "Napíšte vlastný názov okna: (nechajte prázdne pre vypnutie)",
"placeholder": "Napríklad: YouTube Music"
}
},
"like-buttons": {
"default": "Základný",
"force-show": "Vynútiť zobrazenie",
"hide": "Skryť",
"label": "Like tlačítko"
},
"remove-upgrade-button": "Odstrániť tlačidlo povýšiť",
"theme": {
"dialog": {
"button": {
"cancel": "Zrušiť",
"remove": "Odstrániť"
},
"remove-theme": "Ste si istý že chcete odstrániť tento vlastný štýl?",
"remove-theme-message": "Toto odstráni vlastný štýl"
},
"label": "Štýl",
"submenu": {
"import-css-file": "Nahrať vlasný CSS súbor",
"no-theme": "Žiadny štýl"
}
}
}
}
}
},
"plugins": {
"enabled": "Zapnuté",
"new": "NOVÝ"
},
"view": {
"submenu": {
"force-reload": "Natvrdo obnoviť",
"reload": "Obnoviť",
"reset-zoom": "Ozajstná veľkosť",
"toggle-fullscreen": "Prepnúť režim Celej Obrazovky",
"zoom-in": "Priblížiť",
"zoom-out": "Oddialiť"
}
}
},
"tray": {
"next": "Ďalšie",
"play-pause": "Prehrať/Pozastaviť",
"previous": "Predcházdajúce",
"quit": "Skončiť",
"restart": "Reštartovať aplikáciu",
"show": "Zobraziť okno",
"tooltip": {
"default": "YouTube Hudba",
"with-song-info": "Youtube Hudba: {{artist}} - {{title}}"
}
}
}
}

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Konfigurišite prilagođeni izlazni medijski uređaj za pesme",
"menu": {
"device-selector": "Izaberite uređaj"
},
"name": "Prilagođeni izlazni uređaj",
"prompt": {
"device-selector": {
"label": "Izaberite izlazni medijski uređaj koji će se koristiti",
"title": "Izaberite izlazni uređaj"
}
}
},
"disable-autoplay": {
"description": "Numere se pokreću u pauziranom režimu",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "Sakrij preostalo vreme",
"hide-github-button": "Sakrij dugme sa GitHub linkom",
"play-on-youtube-music": "Reprodukuj na YouTube Muzici",
"set-inactivity-timeout": "Podesi tajmer za neaktivnost"
"set-inactivity-timeout": "Podesi tajmer za neaktivnost",
"set-status-display-type": {
"label": "Tekst statusa",
"submenu": {
"artist": "Slušanje {artist}",
"title": "Slušanje {song title}",
"youtube-music": "Slušanje YouTube muzike"
}
}
},
"name": "Discord Bogato Prisustvo",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "Unesi korisnički žeton za ListenBrainz"
},
"scrobble-alternative-artist": "Koristi alternativne izvođače",
"scrobble-alternative-title": "Koristi alternativne naslove",
"scrobble-other-media": "Učetkaj druge medije"
},
@ -821,6 +843,14 @@
"label": "Učini da tekst pesme bude savršeno usklađen",
"tooltip": "Izračunaj do milisekunde prikaz sledeće linije teksta (može malo uticati na učinak)"
},
"preferred-provider": {
"label": "Preferirani dobavljač",
"none": {
"label": "Nijedan",
"tooltip": "Bez preferiranog dobavljača"
},
"tooltip": "Izaberite podrazumevanog dobavljača kog ćete koristiti"
},
"romanization": {
"label": "Romanizuj tekstove pesama",
"tooltip": "Ako je tekst pesme na drugom jeziku, pokušaj da ga prikažeš u latiničnoj varijanti."
@ -853,6 +883,27 @@
"description": "Dodaje dodatak dodirne trake za macOS korisnike",
"name": "Dodirna Traka"
},
"transparent-player": {
"description": "Čini prozor aplikacije transparentnim",
"menu": {
"opacity": {
"label": "Neprozirnost",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Tip",
"submenu": {
"acrylic": "Akrilan",
"mica": "Mika",
"none": "Nijedan",
"tabbed": "Kartice"
}
}
},
"name": "Transparentni plejer"
},
"tuna-obs": {
"description": "Integracija sa OBS-ovim Tuna dodatkom",
"name": "Tuna OBS"

View File

@ -2,14 +2,14 @@
"common": {
"console": {
"plugins": {
"execute-failed": "Misslyckades med att köra plugin {{pluginName}}::{{contextName}}",
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} kördes på {{ms}} ms",
"initialize-failed": "Misslyckades med att initialisera pluginen \"{{pluginName}}\"",
"load-all": "Laddar alla pluginer",
"load-failed": "Misslyckades med att ladda pluginen \"{{pluginName}}\"",
"loaded": "Pluginen \"{{pluginName}}\" laddad",
"unload-failed": "Misslyckades med att avlasta pluginen \"{{pluginName}}\"",
"unloaded": "Pluginen \"{{pluginName}}\" avlastad"
"execute-failed": "Misslyckades med att köra tillägget {{pluginName}}::{{contextName}}",
"executed-at-ms": "Tillägget {{pluginName}}::{{contextName}} kördes på {{ms}}ms",
"initialize-failed": "Misslyckades med att initialisera tillägget \"{{pluginName}}\"",
"load-all": "Laddar alla tillägg",
"load-failed": "Misslyckades med att ladda tillägget \"{{pluginName}}\"",
"loaded": "Tillägget \"{{pluginName}}\" laddades in",
"unload-failed": "Kunde inte inaktivera {{pluginName}}-tillägget",
"unloaded": "{{pluginName}}-tillägget inaktiverat"
}
}
},
@ -21,7 +21,7 @@
"main": {
"console": {
"did-finish-load": {
"dev-tools": "Laddning klar. DevTools öppnad"
"dev-tools": "Laddning slutförd. Utvecklarverktyg öppnad"
},
"i18n": {
"loaded": "i18n laddad"
@ -45,16 +45,16 @@
"dialog": {
"hide-menu-enabled": {
"detail": "Menyn är dold, använd 'Alt' för att visa den (eller 'Escape' om du använder inbyggd meny)",
"message": "Dölj Meny är aktiverat",
"title": "Dölj Meny aktiverad"
"message": "Dölj meny är aktiverad",
"title": "Dölj meny aktiverad"
},
"need-to-restart": {
"buttons": {
"later": "Senare",
"restart-now": "Starta om nu"
},
"detail": "\"{{pluginName}}\" pluginen kräver en omstart för att träda i kraft",
"message": "\"{{pluginName}}\" behöver startas om",
"detail": "\"{{pluginName}}\"-tillägget kräver en omstart för att träda i kraft",
"message": "\"{{pluginName}}\"-tillägget behöver startas om",
"title": "Omstart krävs"
},
"unresponsive": {
@ -84,17 +84,17 @@
"label": "Navigering",
"submenu": {
"copy-current-url": "Kopiera nuvarande länk",
"go-back": "Föregående",
"go-forward": "Nästa",
"quit": "Lämna",
"go-back": "Gå tillbaka",
"go-forward": "Gå framåt",
"quit": "Avsluta",
"restart": "Starta om appen"
}
},
"options": {
"label": "Valmöjligheter",
"label": "Alternativ",
"submenu": {
"advanced-options": {
"label": "Avancerade valmöjligheter",
"label": "Avancerade alternativ",
"submenu": {
"auto-reset-app-cache": "Nollställ appcache när appen startar",
"disable-hardware-acceleration": "Stäng av hårdvaruacceleration",
@ -116,10 +116,10 @@
"auto-update": "Uppdatera automatiskt",
"hide-menu": {
"dialog": {
"message": "Menyn kommer vara dold nästa gång du startar, tryck [Alt] för att visa den (eller [´] om du använder inbyggda app-menyn)",
"message": "Menyn kommer att döljas efter omstart, använd [Alt] för att visa menyn (eller [`] vid användning av menyn inuti applikationen)",
"title": "Dölj meny aktiverad"
},
"label": "Göm menyn"
"label": "Dölj meny"
},
"language": {
"dialog": {
@ -132,7 +132,7 @@
}
},
"resume-on-start": "Fortsätt spela när appen öppnas",
"single-instance-lock": "Lås för enskild instans",
"single-instance-lock": "Lås enskild instans",
"start-at-login": "Starta vid inloggning",
"starting-page": {
"label": "Startsidа",
@ -153,14 +153,14 @@
"custom-window-title": {
"label": "Anpassad titel på fönstret",
"prompt": {
"label": "Skriv in en egen fönstertitel (lämna tomt om du inte vill använda någon)",
"label": "Ange anpassad fönstertitel: (lämna tomt för att inaktivera)",
"placeholder": "Exempelvis: YouTube Music"
}
},
"like-buttons": {
"default": "Standard",
"force-show": "Tvinga fram visning",
"hide": "Göm",
"hide": "Dölj",
"label": "Gilla-knappar"
},
"remove-upgrade-button": "Ta bort knappen för uppgradering",
@ -175,7 +175,7 @@
},
"label": "Tema",
"submenu": {
"import-css-file": "Ladda in en anpassad CSS-fil",
"import-css-file": "Importera anpassad CSS-fil",
"no-theme": "Inget tema"
}
}
@ -185,8 +185,8 @@
},
"plugins": {
"enabled": "Aktiverad",
"label": "Plugins",
"new": "Ny"
"label": "Tillägg",
"new": "NY"
},
"view": {
"label": "Visa",
@ -226,7 +226,7 @@
"name": "Annonsblockerare"
},
"album-actions": {
"description": "Lägger till knappar för Undislike, Dislike, Like och Unlike för att använda detta på alla låtar i en spellista eller ett album",
"description": "Lägger till knappar för Undislike, Dislike, Like och Unlike för att använda detta på alla spår i en spellista eller ett album",
"name": "Albumåtgärder"
},
"album-color-theme": {
@ -245,7 +245,7 @@
"description": "Ger en ljuseffekt genom att försiktigt kasta färger från videon på skärmens bakgrund",
"menu": {
"blur-amount": {
"label": "Osärpa",
"label": "Oskärpa",
"submenu": {
"pixels": "{{blurAmount}} pixlar"
}
@ -281,52 +281,443 @@
}
},
"use-fullscreen": {
"label": "använder helskärm"
"label": "Använder helskärm"
}
},
"name": "Ambiensläge"
},
"amuse": {
"description": "Lägger till stöd för YouTube Music i Amuse Now Playing-widgeten av 6K Labs",
"name": "Amuse",
"response": {
"query": "Amuse API-servern körs. Använd GET /query för att hämta information om låt."
}
},
"api-server": {
"description": "Lägger till en API-server för att styra spelaren",
"dialog": {
"request": {
"buttons": {
"allow": "Tillåt",
"deny": "Avvisa"
},
"message": "Tillåt {{ID}} ({{origin}}) att få åtkomst till API:et?",
"title": "Förfrågan om API-åtkomst"
}
},
"menu": {
"auth-strategy": {
"label": "Metod för åtkomstkontroll",
"submenu": {
"auth-at-first": {
"label": "Ge åtkomst vid första begäran"
},
"none": {
"label": "Ingen åtkomstkontroll"
}
}
},
"hostname": {
"label": "Värdnamn"
},
"port": {
"label": "Port"
}
},
"name": "API-server [Beta]",
"prompt": {
"hostname": {
"label": "Ange värdnamnet (t.ex. 0.0.0.0) för API-servern:",
"title": "Värdnamn"
},
"port": {
"label": "Ange porten för API-servern:",
"title": "Port"
}
}
},
"audio-compressor": {
"description": "Applicera komprimering på ljudet (sänker volymen på de starkaste delarna av signalen och höjer volymen på de svagaste delarna)",
"name": "Ljudkompressor"
},
"auth-proxy-adapter": {
"description": "Stöd för användning av autentiseringsproxy-tjänster",
"menu": {
"disable": "Inaktivera proxy-adapter",
"enable": "Aktivera proxy-adapter",
"hostname": {
"label": "Värdnamn"
},
"port": {
"label": "Port"
}
},
"name": "Adapter För Autentiseringsproxy",
"prompt": {
"hostname": {
"label": "Ange värdnamn för lokal proxyserver (kräver omstart):",
"title": "Proxy-värdnamn"
},
"port": {
"label": "Ange port för lokal proxyserver (kräver omstart):",
"title": "Port för proxy"
}
}
},
"blur-nav-bar": {
"description": "Gör navigeringsfältet transparent och suddigt",
"name": "Suddigt Navigeringsfält"
},
"bypass-age-restrictions": {
"description": "Hoppa över YouTubes åldersverifiering",
"name": "Hoppa Över Åldersbegränsningar"
},
"captions-selector": {
"description": "Välj textning för YouTube Music-ljudspår",
"menu": {
"autoload": "Välj automatiskt senast använda textning",
"disable-captions": "Ingen textning som standard"
},
"name": "Textväljare",
"prompt": {
"selector": {
"label": "Aktuellt textningsspråk: {{language}}",
"none": "Inget",
"title": "Välj textspråk"
}
},
"templates": {
"title": "Öppna textväljaren"
},
"toast": {
"caption-changed": "Textning ändrad till {{language}}",
"caption-disabled": "Textning inaktiverad",
"no-captions": "Inga undertexter tillgängliga för denna låt"
}
},
"compact-sidebar": {
"description": "Sätt alltid sidomenyn i kompakt läge",
"name": "Kompakt Sidomeny"
},
"crossfade": {
"description": "Mjuk övergång mellan låtar",
"menu": {
"advanced": "Avancerat"
},
"name": "Mjuk Övergång [Beta]",
"prompt": {
"options": {
"multi-input": {
"fade-in-duration": "Fade-in-varaktighet (ms)",
"fade-out-duration": "Fade-out-varaktighet (ms)",
"fade-scaling": {
"label": "Fade-skalning",
"linear": "Linjär",
"logarithmic": "Logaritmisk"
},
"seconds-before-end": "Övergång i sekunder före slutet"
},
"title": "Övergångsinställningar"
}
}
},
"custom-output-device": {
"description": "Konfigurera en anpassad utdataenhet för låtar",
"menu": {
"device-selector": "Välj enhet"
},
"name": "Anpassad Utdataenhet",
"prompt": {
"device-selector": {
"label": "Välj den utdataenhet som ska användas",
"title": "Välj utdataenhet"
}
}
},
"disable-autoplay": {
"description": "Starta låt i \"pausat\" läge",
"menu": {
"apply-once": "Gäller endast vid uppstart"
},
"name": "Inaktivera Automatisk Uppspelning"
},
"discord": {
"backend": {
"already-connected": "Försökte ansluta med aktiv anslutning",
"connected": "Ansluten till Discord",
"disconnected": "Frånkopplad från Discord"
},
"description": "Visa dina vänner vad du lyssnar på med Aktivitetsdelning",
"menu": {
"auto-reconnect": "Automatisk återanslutning",
"clear-activity": "Rensa aktivitet",
"clear-activity-after-timeout": "Rensa aktivitet efter tidsgräns",
"connected": "Ansluten",
"disconnected": "Frånkopplad",
"hide-duration-left": "Dölj återstående tid",
"hide-github-button": "Dölj knapp för GitHub-länk",
"play-on-youtube-music": "Spela på YouTube Music",
"set-inactivity-timeout": "Ställ in inaktivitetstid",
"set-status-display-type": {
"label": "Statusmeddelande",
"submenu": {
"artist": "Lyssnar på {artist}",
"title": "Lyssnar på {song title}",
"youtube-music": "Lyssnar på YouTube Music"
}
}
},
"name": "Discord Aktivitetsdelning",
"prompt": {
"set-inactivity-timeout": {
"label": "Ange inaktivitetstid i sekunder:",
"title": "Ställ in inaktivitetstid"
}
}
},
"downloader": {
"backend": {
"dialog": {
"error": {
"buttons": {
"ok": "OK"
},
"message": "Hoppsan! Nedladdningen misslyckades…",
"title": "Fel vid nedladdning!"
},
"start-download-playlist": {
"buttons": {
"ok": "OK"
},
"detail": "({{playlistSize}} låtar)",
"message": "Laddar ner {{playlistTitle}}-spellistan",
"title": "Nedladdning påbörjad"
}
},
"feedback": {
"conversion-progress": "Konvertering: {{percent}}%",
"converting": "Konverterar…",
"done": "Klart: {{filePath}}",
"download-info": "Laddar ner {{artist}} - {{title}} [{{videoId}}",
"download-progress": "Nedladdning: {{percent}}%",
"downloading": "Laddar ner…",
"downloading-counter": "Laddar ner {{current}}/{{total}}…",
"downloading-playlist": "Laddar ner {{playlistTitle}}-spellistan — {{playlistSize}} spår ({{playlistId}})",
"error-while-downloading": "Fel vid nedladdning \"{{author}} - {{title}}\": {{error}}",
"folder-already-exists": "Mappen {{playlistFolder}} finns redan",
"getting-playlist-info": "Hämtar information om spellista…",
"loading": "Laddar…",
"playlist-has-only-one-song": "Spellistan innehåller bara ett objekt. Laddar ner direkt.",
"playlist-id-not-found": "Hittade inget ID för spellista",
"playlist-is-empty": "Spellistan är tom",
"playlist-is-mix-or-private": "Fel vid hämtning av spellisteinformation. Se till att den inte är privat eller en 'Mixed for you'-spellista\n\n{{error}}",
"preparing-file": "Förbereder fil…",
"saving": "Sparar…",
"trying-to-get-playlist-id": "Försöker hämta spelliste-ID: {{playlistId}}",
"video-id-not-found": "Videon hittades inte",
"writing-id3": "Skriver ID3-taggar…"
}
},
"description": "Laddar ner MP3 / originalljud direkt från gränssnittet",
"menu": {
"choose-download-folder": "Välj nedladdningsmapp",
"download-finish-settings": {
"label": "Ladda ner när klart",
"prompt": {
"last-percent": "Efter x procent",
"last-seconds": "Senaste x sekunderna",
"title": "Ställ in när nedladdning ska ske"
},
"submenu": {
"advanced": "Avancerat",
"enabled": "Aktiverad",
"mode": "Tidsläge",
"percent": "Procent",
"seconds": "Sekunder"
}
},
"download-playlist": "Ladda ner spellista",
"presets": "Förinställningar",
"skip-existing": "Hoppa över befintliga filer"
},
"name": "Nedladdare",
"renderer": {
"can-not-update-progress": "Kan inte uppdatera förlopp"
},
"templates": {
"button": "Ladda ner"
}
},
"equalizer": {
"description": "Lägger till en equalizer i spelaren",
"menu": {
"presets": {
"label": "Förinställningar",
"list": {
"bass-booster": "Basförstärkning"
}
}
},
"name": "Equalizer"
},
"exponential-volume": {
"description": "Gör volymreglaget exponentiellt så att det blir lättare att välja lägre volymer.",
"name": "Exponentiell Volym"
},
"in-app-menu": {
"description": "Ger menyrader ett snyggt, mörkt, eller albumfärgat utseende",
"menu": {
"hide-dom-window-controls": "Dölj DOM-fönsterkontroller"
},
"name": "Meny I Appen"
},
"lumiastream": {
"description": "Lägger till stöd för Lumia Stream",
"name": "Lumia Stream [Beta]"
},
"lyrics-genius": {
"description": "Lägger till stöd för texter till de flesta låtar",
"menu": {
"romanized-lyrics": "Romiserade texter"
},
"name": "Texter från Genius",
"renderer": {
"fetched-lyrics": "Hämtade texter från Genius"
}
},
"music-together": {
"description": "Dela en spellista med andra. När värden spelar en låt kommer alla andra höra samma låt",
"dialog": {
"enter-host": "Ange värd-ID"
},
"internal": {
"save": "Spara",
"track-source": "Ljudkälla",
"unknown-user": "Okänd användare"
},
"menu": {
"click-to-copy-id": "Kopiera värd-ID",
"close": "Stäng \"Music Together\"",
"connected-users": "Anslutna användare",
"disconnect": "Koppla från \"Music Together\"",
"empty-user": "Inga anslutna användare",
"host": "Värd för \"Music Together\"",
"join": "Gå med i \"Music Together\"",
"permission": {
"all": "Tillåt gäster att styra spellista och spelare",
"host-only": "Endast värden kan styra spellista och spelare",
"playlist": "Tillåt gäster att styra spellistan"
},
"set-permission": "Ändra behörighet för styrning",
"status": {
"disconnected": "Frånkopplad",
"guest": "Ansluten som gäst",
"host": "Ansluten som värd"
}
},
"name": "Music Together [Beta]",
"toast": {
"add-song-failed": "Misslyckades med att lägga till låt",
"closed": "\"Music Together\" stängdes",
"disconnected": "\"Music Together\" frånkopplad",
"host-failed": "Misslyckades med att vara värd för \"Music Together\"",
"id-copied": "Värd-ID kopierat till urklipp",
"id-copy-failed": "Misslyckades med att kopiera värd-ID till urklipp",
"join-failed": "Misslyckades med att gå med i \"Music Together\"",
"joined": "Gick med i \"Music Together\"",
"permission-changed": "Behörighet för \"Music Together\" ändrad till \"{{permission}}\"",
"remove-song-failed": "Misslyckades med att radera låt",
"user-connected": "{{name}} gick med i \"Music Together\"",
"user-disconnected": "{{name}} lämnade \"Music Together\""
}
},
"navigation": {
"name": "Navigering"
"description": "Direkt integrering av Nästa-/Tillbaka-navigeringspilar i gränssnittet, som i din favoritwebbläsare",
"name": "Navigering",
"templates": {
"back": {
"title": "Gå till föregående sida"
},
"forward": {
"title": "Gå till nästa sida"
}
}
},
"no-google-login": {
"name": "Inget Google Login"
"description": "Ta bort Google-inloggningsknappar och länkar från gränssnittet",
"name": "Ingen Google-inloggning"
},
"notifications": {
"description": "Visa en notis när en låt börjar spelas (interaktiva notiser finns på Windows)",
"menu": {
"interactive": "Interaktiva notiser",
"interactive-settings": {
"label": "Interaktiva inställningar",
"submenu": {
"hide-button-text": "Dölj knapptext",
"refresh-on-play-pause": "Uppdatera vid Play/Pause",
"tray-controls": "Öppna/stäng vid klick i systemfältet"
}
},
"priority": "Notisprioritet",
"toast-style": "Stil för \"toast\"-notiser",
"unpause-notification": "Visa notis när uppspelning återupptas"
},
"name": "Notiser"
},
"performance-improvement": {
"description": "Förbättra prestanda genom att aktivera experimentella skript",
"name": "Prestandaförbättring [Beta]"
},
"picture-in-picture": {
"description": "Tillåter appen att växla till bild-i-bild-läge",
"menu": {
"always-on-top": "Alltid överst",
"hotkey": {
"label": "Snabbkommando",
"prompt": {
"keybind-options": {
"hotkey": "Snabbkommando"
},
"label": "Välj ett snabbkommando för att växla bild-i-bild-läge",
"title": "Bild-I-Bild genväg"
}
}
},
"save-window-position": "Spara fönsterposition",
"save-window-size": "Spara fönsterstorlek",
"use-native-pip": "Använd webbläsarens inbyggda bild-i-bild"
},
"name": "Bild-I-Bild",
"name": "Bild-i-bild",
"templates": {
"button": "Bild-i-bild"
}
},
"playback-speed": {
"description": "Lägger till ett reglage för att ändra uppspelningshastighet",
"name": "Uppspelningshastighet",
"templates": {
"button": "Hasighet"
"button": "Hastighet"
}
},
"precise-volume": {
"description": "Styr ljudstyrkan exakt med mushjul/snabbtangenter, med anpassat skärmlager och justerbara volymsteg",
"menu": {
"arrows-shortcuts": "Kontroller för lokala piltangenter",
"custom-volume-steps": "Ställ in egna volymsteg",
"global-shortcuts": "Globala snabbkommandon"
},
"name": "Noggrann Volymkontroll",
"prompt": {
"global-shortcuts": {
"keybind-options": {
"decrease": "Minska Volym",
"increase": "Öka Volym"
}
"decrease": "Sänk volymen",
"increase": "Öka volymen"
},
"label": "Välj globala kortkommandon för volym:",
"title": "Globala kortkommandon för volym"
},
"volume-steps": {
"label": "Välj volymsteg för ökning/minskning",
"title": "Volymsteg"
}
}
@ -335,56 +726,224 @@
"backend": {
"dialog": {
"quality-changer": {
"detail": "Nuvarande kvalité: {{quality}}",
"message": "Välj Video Kvalité:",
"title": "Välj Video Kvalité"
"detail": "Nuvarande kvalitet: {{quality}}",
"message": "Välj videokvalitet:",
"title": "Välj videokvalitet"
}
}
},
"description": "Tillåter att ändra videokvalitet med en knapp i videons overlay",
"name": "Videokvalitetsväxlare",
"renderer": {
"quality-settings-button": {
"label": "Öppna kvalitetsväxlare för spelaren"
}
}
},
"scrobbler": {
"description": "Lägg till scrobbling-stöd (t.ex. last.fm, Listenbrainz)",
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Misslyckades att autentisera med Last.fm\nDölj popup-fönstret till nästa omstart.",
"title": "Autentisering misslyckades"
}
}
},
"menu": {
"lastfm": {
"api-settings": "Last.fm API-inställningar"
},
"listenbrainz": {
"token": "Ange ListenBrainz användartoken"
},
"scrobble-alternative-artist": "Använd alternativa artister",
"scrobble-alternative-title": "Använd alternativa titlar",
"scrobble-other-media": "Scrobbla annan media"
},
"name": "Scrobbler",
"prompt": {
"lastfm": {
"api-key": "Last.fm API nyckel"
"api-key": "Last.fm API nyckel",
"api-secret": "Last.fm API-hemlighet"
},
"listenbrainz": {
"token": {
"label": "Ange din ListenBrainz användartoken:",
"title": "ListenBrainz token"
}
}
}
},
"shortcuts": {
"description": "Tillåter inställning av globala kortkommandon för uppspelning (spela/pausa/nästa/föregående) och inaktiverar medie-OSD genom att åsidosätta medietangenter. Aktiverar Ctrl/CMD + F för sökning. Aktiverar Linux MPRIS-stöd för medietangenter och anpassade kortkommandon för avancerade användare",
"menu": {
"override-media-keys": "Åsidosätt medietangenter",
"set-keybinds": "Ställ in globala kontroller för låtar"
},
"name": "Genvägar (& MPRIS)",
"prompt": {
"keybind": {
"keybind-options": {
"next": "Nästa",
"play-pause": "Spela / Pausa",
"previous": "Föregående"
}
},
"label": "Välj globala kortkommandon för kontroll av låtar:",
"title": "Globala kortkommandon"
}
}
},
"skip-disliked-songs": {
"description": "Hoppar över låtar du inte gillar",
"name": "Hoppa Över Låtar Du Inte Gillar"
},
"skip-silences": {
"description": "Hoppa automatiskt över tysta partier i låtar",
"name": "Hoppa Över Tysta Partier"
},
"sponsorblock": {
"description": "Hoppar automatiskt över icke-musikdelar som intro/outro eller delar av musikvideor där ingen musik spelas",
"name": "Blockera Sponsorer"
},
"synced-lyrics": {
"description": "Visar synkroniserade låttexter med hjälp av tjänster som LRClib.",
"errors": {
"fetch": "⚠️ Ett fel uppstod när texterna skulle hämtas.\n\tFörsök igen senare.",
"not-found": "⚠️ Inga texter hittades för denna låt."
},
"menu": {
"default-text-string": {
"label": "Standardtecken mellan låttexter",
"tooltip": "Välj standardtecken att använda för mellanrummet mellan låttexter"
},
"line-effect": {
"label": "Linjeeffekt",
"submenu": {
"fancy": {
"label": "Stiligt",
"tooltip": "Använd stora, app-liknande effekter på den aktuella raden"
},
"focus": {
"label": "Fokus",
"tooltip": "Gör endast den aktuella raden vit"
},
"offset": {
"label": "Förskjutning",
"tooltip": "Förskjut den aktuella raden åt höger"
},
"scale": {
"label": "Skala",
"tooltip": "Skala den aktuella raden"
}
},
"tooltip": "Välj effekt att applicera på den aktuella raden"
},
"precise-timing": {
"label": "Gör låttexterna perfekt synkroniserade",
"tooltip": "Beräkna till millisekunden när nästa rad ska visas (kan ha en liten inverkan på prestanda)"
},
"preferred-provider": {
"label": "Föredragen leverantör",
"none": {
"label": "Ingen",
"tooltip": "Ingen föredragen leverantör"
},
"tooltip": "Välj standardleverantör att använda"
},
"romanization": {
"label": "Romanisera låttexter",
"tooltip": "Om låttexterna är på ett annat språk, försök visa en latinsk version."
},
"show-lyrics-even-if-inexact": {
"label": "Visa låttexter även om de inte är exakta",
"tooltip": "Om låten inte hittas försöker tillägget igen med en annan sökförfrågan.\nResultatet från det andra försöket kanske inte är exakt."
},
"show-time-codes": {
"label": "Visa tidskoder",
"tooltip": "Visa tidskoderna bredvid låttexterna"
}
},
"name": "Synkroniserade Låttexter",
"refetch-btn": {
"fetching": "Hämtar...",
"normal": "Hämta låttexter igen"
},
"warnings": {
"duration-mismatch": "⚠️ - Texterna kan vara osynkroniserade på grund av en skillnad i spårlängd.",
"inexact": "⚠️ - Låttexterna för den här låten kanske inte är exakta",
"instrumental": "⚠️ - Det här är en instrumentallåt"
}
},
"taskbar-mediacontrol": {
"description": "Kontrollera uppspelning från aktivitetsfältet i Windows",
"name": "Mediakontroll i aktivitetsfältet"
},
"touchbar": {
"description": "Lägger till en TouchBar-widget för macOS-användare",
"name": "TouchBar"
},
"transparent-player": {
"description": "Gör appfönstret genomskinligt",
"menu": {
"opacity": {
"label": "Opacitet",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Typ",
"submenu": {
"acrylic": "Akryl",
"mica": "Mica",
"none": "Ingen",
"tabbed": "Flikad"
}
}
},
"name": "Genomskinlig Spelare"
},
"tuna-obs": {
"description": "Integration med OBS-pluginprogrammet Tuna",
"name": "Tuna OBS"
},
"unobtrusive-player": {
"description": "Undviker att spelaren visas när musik spelas",
"name": "Diskret Spelare"
},
"video-toggle": {
"description": "Lägger till en knapp för att växla mellan video/musik-läge. Kan också valfritt ta bort hela videofliken",
"menu": {
"align": {
"label": "Justering",
"submenu": {
"left": "Vänster",
"middle": "Mitten",
"right": "Höger"
}
},
"force-hide": "Tvinga borttagning av videoflik",
"mode": {
"label": "Läge",
"submenu": {
"disabled": "Inaktiverad"
"custom": "Anpassad växling",
"disabled": "Inaktiverad",
"native": "Inbyggd växling"
}
}
},
"name": "Video PÅ/AV",
"templates": {
"button-song": "Låt"
"button-song": "Låt",
"button-video": "Video"
}
},
"visualizer": {
"description": "Lägger till en visualisering i spelaren",
"menu": {
"visualizer-type": "Visualiseringstyp"
},
"name": "Visualiserare"
}
}

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "பாடல்களுக்கான தனிப்பயன் வெளியீட்டு ஊடக சாதனத்தை கட்டமைக்கவும்",
"menu": {
"device-selector": "சாதனத்தை தேர்ந்தெடுக்கவும்"
},
"name": "விருப்பமான வெளியிட்டு சாதனம்",
"prompt": {
"device-selector": {
"label": "பயன்படுத்தப்பட வேண்டிய வெளிப்பாட்டு ஊடக சாதனத்தை தேர்ந்தெடுக்கவும்",
"title": "வெளியிட்டு சாதனத்தை தேர்ந்தெடுக்கவும்"
}
}
},
"disable-autoplay": {
"description": "\"இடைநிறுத்தப்பட்ட\" பயன்முறையில் பாடல் தொடங்குகிறது",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "காலம் மீதமுள்ளதை மறைக்கவும்",
"hide-github-button": "அறிவிலிமையம் இணைப்பு பொத்தானை மறைக்கவும்",
"play-on-youtube-music": "யூடியூப் இசையில் விளையாடுங்கள்",
"set-inactivity-timeout": "செயலற்ற நேரம் முடிந்தது"
"set-inactivity-timeout": "செயலற்ற நேரம் முடிந்தது",
"set-status-display-type": {
"label": "நிலை உரை",
"submenu": {
"artist": "{கலைஞர்} பாடலைக் கேட்கிறேன்",
"title": "பாடலைக் கேட்கிறேன்{பாடல் தலைப்பு}",
"youtube-music": "வலையொளி இசையில் கேட்கிறது"
}
}
},
"name": "முரண்பாடு பணக்கார இருப்பு",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "WeskBrainz பயனர் கிள்ளாக்கை உள்ளிடவும்"
},
"scrobble-alternative-artist": "மாற்று கலைஞர்களை பயன்படுத்துங்கள்",
"scrobble-alternative-title": "மாற்று தலைப்புகளைப் பயன்படுத்தவும்",
"scrobble-other-media": "மற்ற ஊடகங்களை வெல்லுங்கள்"
},
@ -821,6 +843,14 @@
"label": "பாடல் வரிகளை சரியாக ஒத்திசைக்கவும்",
"tooltip": "அடுத்த வரியின் காட்சியைக் கணக்கிடுங்கள் (செயல்திறனில் ஒரு சிறிய தாக்கத்தை ஏற்படுத்தும்)"
},
"preferred-provider": {
"label": "விருப்பமான வழங்குநர்",
"none": {
"label": "இல்லை",
"tooltip": "விருப்பமான வழங்குநர் இல்லை"
},
"tooltip": "வழமையான வழங்குநரை தேர்ந்தெடுக்கவும்"
},
"romanization": {
"label": "பாடல் வரிகள் ரோமானியமாக்குங்கள்",
"tooltip": "பாடல் வரிகள் வேறு மொழியில் இருந்தால், லத்தீன் பதிப்பைக் காட்ட முயற்சிக்கவும்."
@ -853,6 +883,27 @@
"description": "MACOS பயனர்களுக்கான டச்ச்பார் விட்செட்டை சேர்க்கிறது",
"name": "டக்பார்"
},
"transparent-player": {
"description": "பயன்பாட்டு சாளரத்தை வெளிப்படையானதாக மாற்றுக",
"menu": {
"opacity": {
"label": "ஒளிபுகாநிலை",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "வகை",
"submenu": {
"acrylic": "மங்கலான திரை தோற்றம்",
"mica": "மெல்லிய ஒளிவிடும் பின்புல தோற்றம் / மின்மினி கண்ணாடி",
"none": "மாற்றமில்லை அல்லது ஒன்றுமில்லை",
"tabbed": "ஒருங்கிணைக்கப்பற்ற பிரிவுகள்"
}
}
},
"name": "வெளிப்படையான சாதனம்"
},
"tuna-obs": {
"description": "OBS இன் சொருகி டுனாவுடன் ஒருங்கிணைப்பு",
"name": "டுனா குறிப்பு"

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "ตั้งค่าอุปกรณ์เสียงออกสำหรับเพลง",
"menu": {
"device-selector": "เลือกอุปกรณ์"
},
"name": "อุปกรณ์เสียงออกที่กำหนดเอง",
"prompt": {
"device-selector": {
"label": "เลือกอุปกรณ์เสียงออกที่ต้องการใช้",
"title": "เลือกอุปกรณ์เสียงออก"
}
}
},
"disable-autoplay": {
"description": "เริ่มเพลงในโหมดหยุดชั่วคราว",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "ซ่อนระยะเวลาที่เหลือ",
"hide-github-button": "ซ่อนปุ่มลิงก์ GitHub",
"play-on-youtube-music": "เล่นบน YouTube Music",
"set-inactivity-timeout": "ตั้งระยะเวลาไม่มีกิจกรรม"
"set-inactivity-timeout": "ตั้งระยะเวลาไม่มีกิจกรรม",
"set-status-display-type": {
"label": "ข้อความสถานะ",
"submenu": {
"artist": "กำลังฟัง {ชื่อนักร้อง}",
"title": "กำลังฟัง {ชื่อเพลง}",
"youtube-music": "กำลังฟัง YouTube Music"
}
}
},
"name": "แสดงกิจกรรมบนดิสคอร์ด",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "ใส่ user token ของ ListenBrainz"
},
"scrobble-alternative-artist": "ใช้ชื่อศิลปินอื่น",
"scrobble-alternative-title": "ใช้ชื่ออื่น",
"scrobble-other-media": "บันทึกการเล่นสื่ออื่นๆ"
},
@ -821,6 +843,14 @@
"label": "ให้เนื้อเพลงตรงกับเพลงเป๊ะๆ",
"tooltip": "คำนวณมิลิวินาทีในการแสดงบรรทัดถัดไป (มีผลเล็กน้อยกับประสิทธิภาพการทำงาน)"
},
"preferred-provider": {
"label": "ผู้ให้บริการที่ต้องการ",
"none": {
"label": "ไม่มี",
"tooltip": "ไม่มีผู้ให้บริการที่ต้องการ"
},
"tooltip": "เลือกผู้ให้บริการที่ต้องการใช้งาน"
},
"romanization": {
"label": "เนื้อเพลงตัวด้วยอักษรโรมัน",
"tooltip": "ถ้าหากเนื้อเพลงอยู่ในภาษาอื่น ลองเปลี่ยนการแสดงผลโดยใช้เวอร์ชั่นลาติน"
@ -853,6 +883,27 @@
"description": "เพิ่ม Widget บน TouchBar สำหรับผู้ใช้ macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "ทำให้หน้าต่างของแอปโปร่งใส",
"menu": {
"opacity": {
"label": "ความทึบแสง",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "ประเภท",
"submenu": {
"acrylic": "อะคริลิก",
"mica": "ไมกา",
"none": "ไม่มี",
"tabbed": "Tabbed"
}
}
},
"name": "ที่เล่นเพลงโปร่งใส"
},
"tuna-obs": {
"description": "ใช้งานร่วมกันกับปลั้กอิน Tuna บน OBS",
"name": "Tuna OBS"

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Şarkılar için özel bir medya çıkış aygıtı ayarlayın",
"menu": {
"device-selector": "Aygıt Seçin"
},
"name": "Özel Çıkış Aygıtı",
"prompt": {
"device-selector": {
"label": "Kullanılacak medya çıkış aygıtını seçin",
"title": ıkış Aygıtını Seçin"
}
}
},
"disable-autoplay": {
"description": "Şarkıların otomatik olarak duraklatılmasını sağlar",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "Kalan süreyi gizle",
"hide-github-button": "GitHub bağlantısını gizle",
"play-on-youtube-music": "YouTube Music de oynat",
"set-inactivity-timeout": "Hareketsizlik zaman aşımını ayarla"
"set-inactivity-timeout": "Hareketsizlik zaman aşımını ayarla",
"set-status-display-type": {
"label": "Durum metni",
"submenu": {
"artist": "{artist} Dinleniyor",
"title": "{song title} Dinleniyor",
"youtube-music": "YouTube Müzik Dinleniyor"
}
}
},
"name": "Discord Etkinlik Durumu",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "ListenBrainz kullanıcı kimliğinizi girin"
},
"scrobble-alternative-artist": "Alternatif sanatçıları kullan",
"scrobble-alternative-title": "Alternatif başlıklar kullan",
"scrobble-other-media": "Diğer medya ortamlarında listele"
},
@ -821,6 +843,14 @@
"label": "Şarkı sözlerini mükemmel şekilde senkronize edin",
"tooltip": "Bir sonraki satırın görüntülenmesini milisaniyesine kadar hesaplayın (performans üzerinde küçük bir etkisi olabilir)"
},
"preferred-provider": {
"label": "Tercih edilen Sağlayıcı",
"none": {
"label": "Hiçbiri",
"tooltip": "Varsayılan sağlayıcı yok"
},
"tooltip": "Kullanmak İçin varsayılan sağlayıcıyı seçin"
},
"romanization": {
"label": "Sözleri Romanize Et",
"tooltip": "Sözler başka bir dilde gözüküyorsa, Latin versiyonunu dene."
@ -853,6 +883,27 @@
"description": "macOS kullanıcıları için bir TouchBar widget'ı ekler",
"name": "TouchBar"
},
"transparent-player": {
"description": "Uygulama penceresini şeffaf yapar",
"menu": {
"opacity": {
"label": "Opaklık",
"submenu": {
"percent": "%{{opacity}}"
}
},
"type": {
"label": "Tür",
"submenu": {
"acrylic": "Akrilik",
"mica": "Mika",
"none": "Hiçbiri",
"tabbed": "Sekmeli"
}
}
},
"name": "Şeffaf Oynatıcı"
},
"tuna-obs": {
"description": "OBS eklentisi Tuna ile entegrasyon sağlar",
"name": "Tuna OBS"

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "Cài đặt cho thiết bị đầu ra tùy chỉnh cho bài hát",
"menu": {
"device-selector": "Chọn thiết bị"
},
"name": "Thiết bị đầu ra tùy chỉnh",
"prompt": {
"device-selector": {
"label": "Chọn thiết bị phát làm đầu ra để dùng",
"title": "Chọn thiết bị đầu ra"
}
}
},
"disable-autoplay": {
"description": "Làm nhạc bắt đầu ở chế độ \"tạm dừng\". Ngoài ra có thể dừng nhạc khi khởi động ứng dụng (nếu có bật tính năng \"Tiếp tục bài hát cuối cùng khi ứng dụng khởi động\")",
"menu": {
@ -444,9 +457,17 @@
"hide-duration-left": "Ẩn thời lượng còn lại",
"hide-github-button": "Ẩn nút liên kết GitHub",
"play-on-youtube-music": "Phát trong Youtube Music",
"set-inactivity-timeout": "Đặt thời gian chờ không hoạt động"
"set-inactivity-timeout": "Đặt thời gian chờ không hoạt động",
"set-status-display-type": {
"label": "Văn bản trạng thái",
"submenu": {
"artist": "Đang nghe nhạc của {artist}",
"title": "Đang nghe nhạc {song title}",
"youtube-music": "Đang nghe Youtube Music"
}
}
},
"name": "Discord Rich Presence",
"name": "Tích hợp trạng thái Discord",
"prompt": {
"set-inactivity-timeout": {
"label": "Nhập thời gian chờ không hoạt động tính bằng giây:",
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "Nhập mã người dùng ListenBrainz"
},
"scrobble-alternative-artist": "Dùng nghệ sĩ thay thế",
"scrobble-alternative-title": "Dùng tiêu đề thay thế",
"scrobble-other-media": "Scrobber nội dung khác"
},
@ -821,6 +843,14 @@
"label": "Làm cho lời bài hát được đồng bộ hoàn hảo",
"tooltip": "Tính toán chính xác đến mili giây thời gian hiển thị dòng tiếp theo (có thể có tác động nhỏ đến hiệu suất)"
},
"preferred-provider": {
"label": "Nhà cung cấp ưa thích",
"none": {
"label": "Không có",
"tooltip": "Không có nhà cung cấp ưu thích"
},
"tooltip": "Chọn nhà cung cấp lời bài hát mặc định để sử dụng"
},
"romanization": {
"label": "Chuyển lời bài hát sang chữ Latin",
"tooltip": "Nếu lời bài hát đang ở ngôn ngữ khác, thử hiển thị phiên bản bảng chữ cái La-tinh."
@ -853,6 +883,27 @@
"description": "Thêm tiện ích TouchBar cho người dùng macOS",
"name": "TouchBar"
},
"transparent-player": {
"description": "Làm cho cửa sổ ứng dụng có hiệu ứng trong suốt",
"menu": {
"opacity": {
"label": "Độ mờ",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "Kiểu nền",
"submenu": {
"acrylic": "Acrylic",
"mica": "Mica",
"none": "Không có",
"tabbed": "Tabbed"
}
}
},
"name": "Trình phát trong suốt"
},
"tuna-obs": {
"description": "Tích hợp với plugin Tuna của OBS",
"name": "Tuna OBS"

View File

@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "配置歌曲的自定义输出媒体设备",
"menu": {
"device-selector": "选择设备"
},
"name": "自定义输出设备",
"prompt": {
"device-selector": {
"label": "选择要使用的输出媒体设备",
"title": "选择输出设备"
}
}
},
"disable-autoplay": {
"description": "让曲目开始时处于 “暂停” 模式",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "隐藏剩余时长",
"hide-github-button": "隐藏 GitHub 链接按钮",
"play-on-youtube-music": "转至 YouTube Music 播放",
"set-inactivity-timeout": "设置非活跃时长"
"set-inactivity-timeout": "设置非活跃时长",
"set-status-display-type": {
"label": "状态文本",
"submenu": {
"artist": "在听 {artist}",
"title": "在听 {song title}",
"youtube-music": "在听 YouTube Music"
}
}
},
"name": "Discord Rich Presence 状态显示",
"prompt": {
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "输入 ListenBrainz 用户令牌"
},
"scrobble-alternative-artist": "使用替代艺术家",
"scrobble-alternative-title": "使用替代标题",
"scrobble-other-media": "记录其他媒体文件"
},
@ -821,6 +843,14 @@
"label": "让滚动歌词完全同步",
"tooltip": "以毫秒精度估算下句歌词的显示时间(可能对性能有小幅影响)"
},
"preferred-provider": {
"label": "首选歌词源",
"none": {
"label": "无",
"tooltip": "没有首选的歌词源"
},
"tooltip": "选择默认要用的源"
},
"romanization": {
"label": "将歌词罗马化",
"tooltip": "如果歌词以不同语言显示,试着展示拉丁字母版本。"
@ -853,6 +883,27 @@
"description": "为 macOS 用户启用 TouchBar 支持",
"name": "TouchBar"
},
"transparent-player": {
"description": "把应用窗口变透明",
"menu": {
"opacity": {
"label": "不透明",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "类型",
"submenu": {
"acrylic": "亚克力",
"mica": "云母",
"none": "无",
"tabbed": "标签"
}
}
},
"name": "透明播放器"
},
"tuna-obs": {
"description": "与 OBS 的 Tuna 插件集成",
"name": "Tuna OBS"

View File

@ -151,9 +151,9 @@
"label": "介面設定",
"submenu": {
"custom-window-title": {
"label": "客化窗口標題",
"label": "客化窗口標題",
"prompt": {
"label": "輸入客化窗口標題: (留空讓其用)",
"label": "輸入客化窗口標題: (留空讓其用)",
"placeholder": "例如: YouTube 音樂"
}
},
@ -421,6 +421,19 @@
}
}
},
"custom-output-device": {
"description": "為歌曲設定自訂輸出媒體裝置",
"menu": {
"device-selector": "選擇裝置"
},
"name": "自訂輸出裝置",
"prompt": {
"device-selector": {
"label": "選擇要使用的輸出媒體裝置",
"title": "選擇輸出裝置"
}
}
},
"disable-autoplay": {
"description": "讓歌曲開始時為暫停模式",
"menu": {
@ -444,7 +457,15 @@
"hide-duration-left": "隱藏音樂剩餘時間狀態",
"hide-github-button": "隱藏 GitHub 頁面按鈕",
"play-on-youtube-music": "顯示 Play on YouTube Music 按鈕",
"set-inactivity-timeout": "設定閒置狀態時長"
"set-inactivity-timeout": "設定閒置狀態時長",
"set-status-display-type": {
"label": "狀態文字",
"submenu": {
"artist": "正在聽 {artist}",
"title": "正在聽 {song title}",
"youtube-music": "正在聽 YouTube Music"
}
}
},
"name": "Discord 狀態",
"prompt": {
@ -645,8 +666,8 @@
"name": "歌曲播放通知"
},
"performance-improvement": {
"description": "使用實驗性的腳本以化效能",
"name": "效能化 [Beta]"
"description": "使用實驗性的腳本以最佳化效能",
"name": "效能最佳化 [Beta]"
},
"picture-in-picture": {
"description": "允許應用程式切換至子母畫面模式",
@ -724,7 +745,7 @@
"dialog": {
"lastfm": {
"auth-failed": {
"message": "Last.fm 認證失敗\n將隱藏彈窗直到重啟。",
"message": "Last.fm 認證失敗\n將隱藏彈出視窗直到重啟。",
"title": "認證失敗"
}
}
@ -736,6 +757,7 @@
"listenbrainz": {
"token": "輸入 ListenBrainz 使用者憑證"
},
"scrobble-alternative-artist": "使用替代的藝人",
"scrobble-alternative-title": "使用替代歌曲標題",
"scrobble-other-media": "紀錄其他媒體檔案"
},
@ -743,7 +765,7 @@
"prompt": {
"lastfm": {
"api-key": "Last.fm API 金鑰",
"api-secret": "Last.fm API 鑰"
"api-secret": "Last.fm API 鑰"
},
"listenbrainz": {
"token": {
@ -821,6 +843,14 @@
"label": "使歌詞完美同步",
"tooltip": "更精確的計算下一行歌詞的顯示 (將會降低些許效能)"
},
"preferred-provider": {
"label": "偏好提供者",
"none": {
"label": "無",
"tooltip": "沒有偏好的提供者"
},
"tooltip": "選擇要使用的預設提供者"
},
"romanization": {
"label": "羅馬拼音化歌詞",
"tooltip": "如果歌詞使用不同語言,嘗試使用拉丁文顯示。"
@ -853,6 +883,27 @@
"description": "為 macOS 使用者新增觸控列支援",
"name": "觸控列 (Touchbar) 支援"
},
"transparent-player": {
"description": "讓應用程式視窗變為透明",
"menu": {
"opacity": {
"label": "不透明度",
"submenu": {
"percent": "{{opacity}}%"
}
},
"type": {
"label": "類型",
"submenu": {
"acrylic": "壓克力",
"mica": "雲母",
"none": "無",
"tabbed": "分頁式"
}
}
},
"name": "透明撥放器"
},
"tuna-obs": {
"description": "與 OBS 的 Tuna 外掛連線",
"name": "Tuna OBS"

View File

@ -15,7 +15,7 @@ import {
type BrowserWindowConstructorOptions,
} from 'electron';
import enhanceWebRequest, {
BetterSession,
type BetterSession,
} from '@jellybrick/electron-better-web-request';
import is from 'electron-is';
import unhandled from 'electron-unhandled';
@ -29,7 +29,7 @@ import { allPlugins, mainPlugins } from 'virtual:plugins';
import { languageResources } from 'virtual:i18n';
import config from '@/config';
import * as config from '@/config';
import { refreshMenu, setApplicationMenu } from '@/menu';
import { fileExists, injectCSS, injectCSSAsFile } from '@/plugins/utils/main';
@ -61,13 +61,6 @@ import { defaultAuthProxyConfig } from '@/plugins/auth-proxy-adapter/config';
import type { PluginConfig } from '@/types/plugins';
if (!is.macOS()) {
delete (await allPlugins())['touchbar'];
}
if (!is.windows()) {
delete (await allPlugins())['taskbar-mediacontrol'];
}
// Catch errors and log them
unhandled({
logger: console.error,
@ -356,10 +349,12 @@ async function createMainWindow() {
delete decorations.titleBarStyle;
}
const win = new BrowserWindow({
const electronWindowSettings: Electron.BrowserWindowConstructorOptions = {
icon,
width: windowSize.width,
height: windowSize.height,
minWidth: 325,
minHeight: 425,
backgroundColor: '#000',
show: false,
webPreferences: {
@ -374,7 +369,10 @@ async function createMainWindow() {
}),
},
...decorations,
});
};
const win = new BrowserWindow(electronWindowSettings);
await initHook(win);
initTheme(win);

View File

@ -1,9 +1,9 @@
import { BrowserWindow, ipcMain } from 'electron';
import { type BrowserWindow, ipcMain } from 'electron';
import { deepmerge } from 'deepmerge-ts';
import { allPlugins, mainPlugins } from 'virtual:plugins';
import config from '@/config';
import * as config from '@/config';
import { LoggerPrefix, startPlugin, stopPlugin } from '@/utils';
import { t } from '@/i18n';

View File

@ -1,7 +1,7 @@
import { deepmerge } from 'deepmerge-ts';
import { allPlugins } from 'virtual:plugins';
import config from '@/config';
import * as config from '@/config';
import { setApplicationMenu } from '@/menu';
import { LoggerPrefix } from '@/utils';

View File

@ -3,7 +3,7 @@ import { allPlugins, preloadPlugins } from 'virtual:plugins';
import { LoggerPrefix, startPlugin, stopPlugin } from '@/utils';
import config from '@/config';
import * as config from '@/config';
import { t } from '@/i18n';

View File

@ -1,11 +1,11 @@
import is from 'electron-is';
import {
app,
BrowserWindow,
type BrowserWindow,
clipboard,
dialog,
Menu,
MenuItem,
type MenuItem,
shell,
} from 'electron';
import prompt from 'custom-electron-prompt';
@ -15,7 +15,7 @@ import { allPlugins } from 'virtual:plugins';
import { languageResources } from 'virtual:i18n';
import config from './config';
import * as config from './config';
import { restart } from './providers/app-controls';
import { startingPages } from './providers/extracted-data';

View File

@ -1 +0,0 @@
/ad-blocker-engine.bin

View File

@ -1,58 +0,0 @@
function skipAd(target: Element) {
const skipButton = target.querySelector<HTMLButtonElement>(
'button.ytp-ad-skip-button-modern',
);
if (skipButton) {
skipButton.click();
}
}
function speedUpAndMute(player: Element, isAdShowing: boolean) {
const video = player.querySelector<HTMLVideoElement>('video');
if (!video) return;
if (isAdShowing) {
video.playbackRate = 16;
video.muted = true;
} else if (!isAdShowing) {
video.playbackRate = 1;
video.muted = false;
}
}
export const loadAdSpeedup = () => {
const player = document.querySelector<HTMLVideoElement>('#movie_player');
if (!player) return;
new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (
mutation.type === 'attributes' &&
mutation.attributeName === 'class'
) {
const target = mutation.target as HTMLElement;
const isAdShowing =
target.classList.contains('ad-showing') ||
target.classList.contains('ad-interrupting');
speedUpAndMute(target, isAdShowing);
}
if (
mutation.type === 'childList' &&
mutation.addedNodes.length &&
mutation.target instanceof HTMLElement
) {
skipAd(mutation.target);
}
}
}).observe(player, {
attributes: true,
childList: true,
subtree: true,
});
const isAdShowing =
player.classList.contains('ad-showing') ||
player.classList.contains('ad-interrupting');
speedUpAndMute(player, isAdShowing);
skipAd(player);
};

View File

@ -1,81 +0,0 @@
// Used for caching
import path from 'node:path';
import fs, { promises } from 'node:fs';
import { ElectronBlocker } from '@ghostery/adblocker-electron';
import { app, net } from 'electron';
const SOURCES = [
'https://raw.githubusercontent.com/kbinani/adblock-youtube-ads/master/signed.txt',
// UBlock Origin
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/filters.txt',
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/quick-fixes.txt',
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/unbreak.txt',
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/filters-2020.txt',
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/filters-2021.txt',
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/filters-2022.txt',
'https://raw.githubusercontent.com/ghostery/adblocker/master/packages/adblocker/assets/ublock-origin/filters-2023.txt',
// Fanboy Annoyances
'https://secure.fanboy.co.nz/fanboy-annoyance_ubo.txt',
// AdGuard
'https://filters.adtidy.org/extension/ublock/filters/122_optimized.txt',
];
let blocker: ElectronBlocker | undefined;
export const loadAdBlockerEngine = async (
session: Electron.Session | undefined = undefined,
cache: boolean = true,
additionalBlockLists: string[] = [],
disableDefaultLists: boolean | unknown[] = false,
) => {
// Only use cache if no additional blocklists are passed
const cacheDirectory = path.join(app.getPath('userData'), 'adblock_cache');
if (!fs.existsSync(cacheDirectory)) {
fs.mkdirSync(cacheDirectory);
}
const cachingOptions =
cache && additionalBlockLists.length === 0
? {
path: path.join(cacheDirectory, 'adblocker-engine.bin'),
read: promises.readFile,
write: promises.writeFile,
}
: undefined;
const lists = [
...((disableDefaultLists && !Array.isArray(disableDefaultLists)) ||
(Array.isArray(disableDefaultLists) && disableDefaultLists.length > 0)
? []
: SOURCES),
...additionalBlockLists,
];
try {
blocker = await ElectronBlocker.fromLists(
(url: string) => net.fetch(url),
lists,
{
enableCompression: true,
// When generating the engine for caching, do not load network filters
// So that enhancing the session works as expected
// Allowing to define multiple webRequest listeners
loadNetworkFilters: session !== undefined,
},
cachingOptions,
);
if (session) {
blocker.enableBlockingInSession(session);
}
} catch (error) {
console.error('Error loading adBlocker engine', error);
}
};
export const unloadAdBlockerEngine = (session: Electron.Session) => {
if (blocker) {
blocker.disableBlockingInSession(session);
}
};
export const isBlockerEnabled = (session: Electron.Session) =>
blocker !== undefined && blocker.isBlockingEnabled(session);

View File

@ -1,148 +0,0 @@
import { contextBridge, webFrame } from 'electron';
import { blockers } from './types';
import { createPlugin } from '@/utils';
import {
isBlockerEnabled,
loadAdBlockerEngine,
unloadAdBlockerEngine,
} from './blocker';
import { inject, isInjected } from './injectors/inject';
import { loadAdSpeedup } from './adSpeedup';
import { t } from '@/i18n';
import type { BrowserWindow } from 'electron';
interface AdblockerConfig {
/**
* Whether to enable the adblocker.
* @default true
*/
enabled: boolean;
/**
* When enabled, the adblocker will cache the blocklists.
* @default true
*/
cache: boolean;
/**
* Which adblocker to use.
* @default blockers.InPlayer
*/
blocker: (typeof blockers)[keyof typeof blockers];
/**
* Additional list of filters to use.
* @example ["https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt"]
* @default []
*/
additionalBlockLists: string[];
/**
* Disable the default blocklists.
* @default false
*/
disableDefaultLists: boolean;
}
export default createPlugin({
name: () => t('plugins.adblocker.name'),
description: () => t('plugins.adblocker.description'),
restartNeeded: false,
config: {
enabled: true,
cache: true,
blocker: blockers.InPlayer,
additionalBlockLists: [],
disableDefaultLists: false,
} as AdblockerConfig,
menu: async ({ getConfig, setConfig }) => {
const config = await getConfig();
return [
{
label: t('plugins.adblocker.menu.blocker'),
submenu: Object.values(blockers).map((blocker) => ({
label: blocker,
type: 'radio',
checked: (config.blocker || blockers.WithBlocklists) === blocker,
click() {
setConfig({ blocker });
},
})),
},
];
},
renderer: {
async onPlayerApiReady(_, { getConfig }) {
const config = await getConfig();
if (config.blocker === blockers.AdSpeedup) {
loadAdSpeedup();
}
},
},
backend: {
mainWindow: null as BrowserWindow | null,
async start({ getConfig, window }) {
const config = await getConfig();
this.mainWindow = window;
if (config.blocker === blockers.WithBlocklists) {
await loadAdBlockerEngine(
window.webContents.session,
config.cache,
config.additionalBlockLists,
config.disableDefaultLists,
);
}
},
stop({ window }) {
if (isBlockerEnabled(window.webContents.session)) {
unloadAdBlockerEngine(window.webContents.session);
}
},
async onConfigChange(newConfig) {
if (this.mainWindow) {
if (
newConfig.blocker === blockers.WithBlocklists &&
!isBlockerEnabled(this.mainWindow.webContents.session)
) {
await loadAdBlockerEngine(
this.mainWindow.webContents.session,
newConfig.cache,
newConfig.additionalBlockLists,
newConfig.disableDefaultLists,
);
}
}
},
},
preload: {
// see #1478
script: `const _prunerFn = window._pruner;
window._pruner = undefined;
JSON.parse = new Proxy(JSON.parse, {
apply() {
return _prunerFn(Reflect.apply(...arguments));
},
});
Response.prototype.json = new Proxy(Response.prototype.json, {
apply() {
return Reflect.apply(...arguments).then((o) => _prunerFn(o));
},
}); 0`,
async start({ getConfig }) {
const config = await getConfig();
if (config.blocker === blockers.InPlayer && !isInjected()) {
inject(contextBridge);
await webFrame.executeJavaScript(this.script);
}
},
async onConfigChange(newConfig) {
if (newConfig.blocker === blockers.InPlayer && !isInjected()) {
inject(contextBridge);
await webFrame.executeJavaScript(this.script);
}
},
},
});

View File

@ -1,3 +0,0 @@
export default async () => {
await import('@ghostery/adblocker-electron-preload');
};

View File

@ -1,5 +0,0 @@
import type { ContextBridge } from 'electron';
export const inject: (contextBridge: ContextBridge) => void;
export const isInjected: () => boolean;

View File

@ -1,259 +0,0 @@
/* eslint-disable */
// Source: https://addons.mozilla.org/en-US/firefox/addon/adblock-for-youtube/
// https://robwu.nl/crxviewer/?crx=https%3A%2F%2Faddons.mozilla.org%2Fen-US%2Ffirefox%2Faddon%2Fadblock-for-youtube%2F
/*
Parts of this code is derived from set-constant.js:
https://github.com/gorhill/uBlock/blob/5de0ce975753b7565759ac40983d31978d1f84ca/assets/resources/scriptlets.js#L704
*/
let injected = false;
export const isInjected = () => injected;
/**
* @param {Electron.ContextBridge} contextBridge
* @returns {*}
*/
export const inject = (contextBridge) => {
injected = true;
{
const pruner = function (o) {
delete o.playerAds;
delete o.adPlacements;
delete o.adSlots;
//
if (o.playerResponse) {
delete o.playerResponse.playerAds;
delete o.playerResponse.adPlacements;
delete o.playerResponse.adSlots;
}
if (o.ytInitialPlayerResponse) {
delete o.ytInitialPlayerResponse.playerAds;
delete o.ytInitialPlayerResponse.adPlacements;
delete o.ytInitialPlayerResponse.adSlots;
}
//
return o;
}
contextBridge.exposeInMainWorld('_pruner', pruner);
}
const chains = [
{
chain: 'playerResponse.adPlacements',
cValue: 'undefined',
},
{
chain: 'ytInitialPlayerResponse.playerAds',
cValue: 'undefined',
},
{
chain: 'ytInitialPlayerResponse.adPlacements',
cValue: 'undefined',
},
{
chain: 'ytInitialPlayerResponse.adSlots',
cValue: 'undefined',
}
];
chains.forEach(function ({ chain, cValue }) {
const thisScript = document.currentScript;
//
switch (cValue) {
case 'null': {
cValue = null;
break;
}
case "''": {
cValue = '';
break;
}
case 'true': {
cValue = true;
break;
}
case 'false': {
cValue = false;
break;
}
case 'undefined': {
cValue = undefined;
break;
}
case 'noopFunc': {
cValue = function () {};
break;
}
case 'trueFunc': {
cValue = function () {
return true;
};
break;
}
case 'falseFunc': {
cValue = function () {
return false;
};
break;
}
default: {
if (/^\d+$/.test(cValue)) {
cValue = Number.parseFloat(cValue);
//
if (isNaN(cValue)) {
return;
}
if (Math.abs(cValue) > 0x7f_ff) {
return;
}
} else {
return;
}
}
}
//
let aborted = false;
const mustAbort = function (v) {
if (aborted) {
return true;
}
aborted =
v !== undefined &&
v !== null &&
cValue !== undefined &&
cValue !== null &&
typeof v !== typeof cValue;
return aborted;
};
/*
Support multiple trappers for the same property:
https://github.com/uBlockOrigin/uBlock-issues/issues/156
*/
const trapProp = function (owner, prop, configurable, handler) {
if (handler.init(owner[prop]) === false) {
return;
}
//
const odesc = Object.getOwnPropertyDescriptor(owner, prop);
let previousGetter;
let previousSetter;
if (odesc instanceof Object) {
if (odesc.configurable === false) {
return;
}
if (odesc.get instanceof Function) {
previousGetter = odesc.get;
}
if (odesc.set instanceof Function) {
previousSetter = odesc.set;
}
}
//
Object.defineProperty(owner, prop, {
configurable,
get() {
if (previousGetter !== undefined) {
previousGetter();
}
//
return handler.getter();
},
set(a) {
if (previousSetter !== undefined) {
previousSetter(a);
}
//
handler.setter(a);
},
});
};
const trapChain = function (owner, chain) {
const pos = chain.indexOf('.');
if (pos === -1) {
trapProp(owner, chain, false, {
v: undefined,
getter() {
return document.currentScript === thisScript ? this.v : cValue;
},
setter(a) {
if (mustAbort(a) === false) {
return;
}
cValue = a;
},
init(v) {
if (mustAbort(v)) {
return false;
}
//
this.v = v;
return true;
},
});
//
return;
}
//
const prop = chain.slice(0, pos);
const v = owner[prop];
//
chain = chain.slice(pos + 1);
if (v instanceof Object || (typeof v === 'object' && v !== null)) {
trapChain(v, chain);
return;
}
//
trapProp(owner, prop, true, {
v: undefined,
getter() {
return this.v;
},
setter(a) {
this.v = a;
if (a instanceof Object) {
trapChain(a, chain);
}
},
init(v) {
this.v = v;
return true;
},
});
};
//
trapChain(window, chain);
});
};

View File

@ -1,5 +0,0 @@
export const blockers = {
WithBlocklists: 'With blocklists',
InPlayer: 'In player',
AdSpeedup: 'Ad speedup',
} as const;

View File

@ -81,26 +81,26 @@ export default createPlugin<
<>
<Show when={showUnDislike()}>
<UnDislikeButton
onClick={this.loadFullList}
maskSize={unDislikeMaskSize()}
onClick={this.loadFullList}
/>
</Show>
<Show when={showDislike()}>
<DislikeButton
onClick={this.loadFullList}
maskSize={dislikeMaskSize()}
onClick={this.loadFullList}
/>
</Show>
<Show when={showLike()}>
<LikeButton
onClick={this.loadFullList}
maskSize={likeMaskSize()}
onClick={this.loadFullList}
/>
</Show>
<Show when={showUnLike()}>
<UnLikeButton
onClick={this.loadFullList}
maskSize={unLikeMaskSize()}
onClick={this.loadFullList}
/>
</Show>
</>

View File

@ -6,22 +6,23 @@ export interface DislikeButtonProps {
export const DislikeButton = (props: DislikeButtonProps) => (
<div class="style-scope">
<button
id="alldislike"
data-type="dislike"
data-filled="false"
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
aria-pressed="false"
aria-label="Dislike all"
aria-pressed="false"
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
data-filled="false"
data-type="dislike"
id="alldislike"
onClick={(e) => props.onClick?.(e)}
>
<div
aria-hidden="true"
class="yt-spec-button-shape-next__icon"
style={{
'color': 'var(--ytmusic-setting-item-toggle-active)',
}}
aria-hidden="true"
>
<div
aria-hidden="true"
class="yt-spec-button-shape-next__icon"
style={{
'color': 'white',
@ -32,24 +33,23 @@ export const DislikeButton = (props: DislikeButtonProps) => (
'z-index': 1,
'position': 'absolute',
}}
aria-hidden="true"
>
<div style={{ 'width': '24px', 'height': '24px' }}>
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
class="style-scope yt-icon"
preserveAspectRatio="xMidYMid meet"
style={{
'pointer-events': 'none',
'display': 'block',
'width': '100%',
'height': '100%',
}}
viewBox="0 0 24 24"
>
<g class="style-scope yt-icon">
<path
d="M18,4h3v10h-3V4z M5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21c0.58,0,1.14-0.24,1.52-0.65L17,14V4H6.57 C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14z"
class="style-scope yt-icon"
d="M18,4h3v10h-3V4z M5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21c0.58,0,1.14-0.24,1.52-0.65L17,14V4H6.57 C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14z"
/>
</g>
</svg>
@ -62,20 +62,20 @@ export const DislikeButton = (props: DislikeButtonProps) => (
}}
>
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
class="style-scope yt-icon"
preserveAspectRatio="xMidYMid meet"
style={{
'pointer-events': 'none',
'display': 'block',
'width': '100%',
'height': '100%',
}}
viewBox="0 0 24 24"
>
<g class="style-scope yt-icon">
<path
d="M18,4h3v10h-3V4z M5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21c0.58,0,1.14-0.24,1.52-0.65L17,14V4H6.57 C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14z"
class="style-scope yt-icon"
d="M18,4h3v10h-3V4z M5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21c0.58,0,1.14-0.24,1.52-0.65L17,14V4H6.57 C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14z"
/>
</g>
</svg>
@ -87,8 +87,8 @@ export const DislikeButton = (props: DislikeButtonProps) => (
}}
>
<div
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
aria-hidden="true"
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
>
<div class="yt-spec-touch-feedback-shape__stroke" />
<div class="yt-spec-touch-feedback-shape__fill" />

View File

@ -6,22 +6,23 @@ export interface LikeButtonProps {
export const LikeButton = (props: LikeButtonProps) => (
<div class="style-scope">
<button
id="alllike"
data-type="like"
data-filled="false"
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
aria-pressed="false"
aria-label="Like all"
aria-pressed="false"
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
data-filled="false"
data-type="like"
id="alllike"
onClick={(e) => props.onClick?.(e)}
>
<div
aria-hidden="true"
class="yt-spec-button-shape-next__icon"
style={{
'color': 'var(--ytmusic-setting-item-toggle-active)',
}}
aria-hidden="true"
>
<div
aria-hidden="true"
class="yt-spec-button-shape-next__icon"
style={{
'color': 'white',
@ -32,24 +33,23 @@ export const LikeButton = (props: LikeButtonProps) => (
'z-index': 1,
'position': 'absolute',
}}
aria-hidden="true"
>
<div style={{ 'width': '24px', 'height': '24px' }}>
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
class="style-scope yt-icon"
preserveAspectRatio="xMidYMid meet"
style={{
'pointer-events': 'none',
'display': 'block',
'width': '100%',
'height': '100%',
}}
viewBox="0 0 24 24"
>
<g class="style-scope yt-icon">
<path
d="M3,11h3v10H3V11z M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11v10h10.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z"
class="style-scope yt-icon"
d="M3,11h3v10H3V11z M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11v10h10.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z"
/>
</g>
</svg>
@ -57,20 +57,20 @@ export const LikeButton = (props: LikeButtonProps) => (
</div>
<div style={{ 'width': '24px', 'height': '24px' }}>
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
class="style-scope yt-icon"
preserveAspectRatio="xMidYMid meet"
style={{
'pointer-events': 'none',
'display': 'block',
'width': '100%',
'height': '100%',
}}
viewBox="0 0 24 24"
>
<g class="style-scope yt-icon">
<path
d="M3,11h3v10H3V11z M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11v10h10.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z"
class="style-scope yt-icon"
d="M3,11h3v10H3V11z M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11v10h10.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z"
/>
</g>
</svg>
@ -78,8 +78,8 @@ export const LikeButton = (props: LikeButtonProps) => (
</div>
<yt-touch-feedback-shape style={{ 'border-radius': 'inherit' }}>
<div
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
aria-hidden="true"
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
>
<div class="yt-spec-touch-feedback-shape__stroke" />
<div class="yt-spec-touch-feedback-shape__fill" />

View File

@ -6,22 +6,23 @@ export interface UnDislikeButtonProps {
export const UnDislikeButton = (props: UnDislikeButtonProps) => (
<div class="style-scope">
<button
id="allundislike"
data-type="dislike"
data-filled="true"
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
aria-pressed="false"
aria-label="Undislike all"
aria-pressed="false"
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
data-filled="true"
data-type="dislike"
id="allundislike"
onClick={(e) => props.onClick?.(e)}
>
<div
aria-hidden="true"
class="yt-spec-button-shape-next__icon"
style={{
color: 'var(--ytmusic-setting-item-toggle-active)',
}}
aria-hidden="true"
>
<div
aria-hidden="true"
class="yt-spec-button-shape-next__icon"
style={{
'color': 'white',
@ -32,7 +33,6 @@ export const UnDislikeButton = (props: UnDislikeButtonProps) => (
'z-index': 1,
'position': 'absolute',
}}
aria-hidden="true"
>
<div
style={{
@ -41,20 +41,20 @@ export const UnDislikeButton = (props: UnDislikeButtonProps) => (
}}
>
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
class="style-scope yt-icon"
preserveAspectRatio="xMidYMid meet"
style={{
'pointer-events': 'none',
'display': 'block',
'width': '100%',
'height': '100%',
}}
viewBox="0 0 24 24"
>
<g class="style-scope yt-icon">
<path
d="M17,4h-1H6.57C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21 c0.58,0,1.14-0.24,1.52-0.65L17,14h4V4H17z M10.4,19.67C10.21,19.88,9.92,20,9.62,20c-0.26,0-0.5-0.11-0.63-0.3 c-0.07-0.1-0.15-0.26-0.09-0.47l1.52-4.94l0.4-1.29H9.46H5.23c-0.41,0-0.8-0.17-1.03-0.46c-0.12-0.15-0.25-0.4-0.18-0.72l1.34-6 C5.46,5.35,5.97,5,6.57,5H16v8.61L10.4,19.67z M20,13h-3V5h3V13z"
class="style-scope yt-icon"
d="M17,4h-1H6.57C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21 c0.58,0,1.14-0.24,1.52-0.65L17,14h4V4H17z M10.4,19.67C10.21,19.88,9.92,20,9.62,20c-0.26,0-0.5-0.11-0.63-0.3 c-0.07-0.1-0.15-0.26-0.09-0.47l1.52-4.94l0.4-1.29H9.46H5.23c-0.41,0-0.8-0.17-1.03-0.46c-0.12-0.15-0.25-0.4-0.18-0.72l1.34-6 C5.46,5.35,5.97,5,6.57,5H16v8.61L10.4,19.67z M20,13h-3V5h3V13z"
/>
</g>
</svg>
@ -67,20 +67,20 @@ export const UnDislikeButton = (props: UnDislikeButtonProps) => (
}}
>
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
class="style-scope yt-icon"
preserveAspectRatio="xMidYMid meet"
style={{
'pointer-events': 'none',
'display': 'block',
'width': '100%',
'height': '100%',
}}
viewBox="0 0 24 24"
>
<g class="style-scope yt-icon">
<path
d="M17,4h-1H6.57C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21 c0.58,0,1.14-0.24,1.52-0.65L17,14h4V4H17z M10.4,19.67C10.21,19.88,9.92,20,9.62,20c-0.26,0-0.5-0.11-0.63-0.3 c-0.07-0.1-0.15-0.26-0.09-0.47l1.52-4.94l0.4-1.29H9.46H5.23c-0.41,0-0.8-0.17-1.03-0.46c-0.12-0.15-0.25-0.4-0.18-0.72l1.34-6 C5.46,5.35,5.97,5,6.57,5H16v8.61L10.4,19.67z M20,13h-3V5h3V13z"
class="style-scope yt-icon"
d="M17,4h-1H6.57C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21 c0.58,0,1.14-0.24,1.52-0.65L17,14h4V4H17z M10.4,19.67C10.21,19.88,9.92,20,9.62,20c-0.26,0-0.5-0.11-0.63-0.3 c-0.07-0.1-0.15-0.26-0.09-0.47l1.52-4.94l0.4-1.29H9.46H5.23c-0.41,0-0.8-0.17-1.03-0.46c-0.12-0.15-0.25-0.4-0.18-0.72l1.34-6 C5.46,5.35,5.97,5,6.57,5H16v8.61L10.4,19.67z M20,13h-3V5h3V13z"
/>
</g>
</svg>
@ -92,8 +92,8 @@ export const UnDislikeButton = (props: UnDislikeButtonProps) => (
}}
>
<div
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
aria-hidden="true"
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
>
<div class="yt-spec-touch-feedback-shape__stroke" />
<div class="yt-spec-touch-feedback-shape__fill" />

View File

@ -6,22 +6,23 @@ export interface UnLikeButtonProps {
export const UnLikeButton = (props: UnLikeButtonProps) => (
<div class="style-scope">
<button
id="allunlike"
data-type="like"
data-filled="true"
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
aria-pressed="false"
aria-label="Unlike all"
aria-pressed="false"
class="like-menu yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-button"
data-filled="true"
data-type="like"
id="allunlike"
onClick={(e) => props.onClick?.(e)}
>
<div
aria-hidden="true"
class="yt-spec-button-shape-next__icon"
style={{
'color': 'var(--ytmusic-setting-item-toggle-active)',
}}
aria-hidden="true"
>
<div
aria-hidden="true"
class="yt-spec-button-shape-next__icon"
style={{
'color': 'white',
@ -32,7 +33,6 @@ export const UnLikeButton = (props: UnLikeButtonProps) => (
'z-index': 1,
'position': 'absolute',
}}
aria-hidden="true"
>
<div
style={{
@ -41,20 +41,20 @@ export const UnLikeButton = (props: UnLikeButtonProps) => (
}}
>
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
class="style-scope yt-icon"
preserveAspectRatio="xMidYMid meet"
style={{
'pointer-events': 'none',
'display': 'block',
'width': '100%',
'height': '100%',
}}
viewBox="0 0 24 24"
>
<g class="style-scope yt-icon">
<path
d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"
class="style-scope yt-icon"
d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"
/>
</g>
</svg>
@ -67,20 +67,20 @@ export const UnLikeButton = (props: UnLikeButtonProps) => (
}}
>
<svg
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
class="style-scope yt-icon"
preserveAspectRatio="xMidYMid meet"
style={{
'pointer-events': 'none',
'display': 'block',
'width': '100%',
'height': '100%',
}}
viewBox="0 0 24 24"
>
<g class="style-scope yt-icon">
<path
d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"
class="style-scope yt-icon"
d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"
/>
</g>
</svg>
@ -92,8 +92,8 @@ export const UnLikeButton = (props: UnLikeButtonProps) => (
}}
>
<div
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
aria-hidden="true"
class="yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response"
>
<div class="yt-spec-touch-feedback-shape__stroke" />
<div class="yt-spec-touch-feedback-shape__fill" />

View File

@ -1,5 +1,5 @@
import { FastAverageColor } from 'fast-average-color';
import Color, { ColorInstance } from 'color';
import Color, { type ColorInstance } from 'color';
import style from './style.css?inline';
@ -31,7 +31,7 @@ export default createPlugin<
alpha?: number,
ratioMultiply?: number,
): string;
updateColor(): void;
updateColor(alpha: number): void;
},
{
enabled: boolean;
@ -143,7 +143,16 @@ export default createPlugin<
document.documentElement.style.setProperty(DARK_COLOR_KEY, '0, 0, 0');
}
this.updateColor();
let alpha: number | null = null;
if (await window.mainConfig.plugins.isEnabled('transparent-player')) {
const value: unknown = window.mainConfig.get(
'plugins.transparent-player.opacity',
);
if (typeof value === 'number' && value >= 0 && value <= 1) {
alpha = value;
}
}
this.updateColor(alpha ?? 1);
});
},
onConfigChange(config) {
@ -163,7 +172,7 @@ export default createPlugin<
}
return `color-mix(in srgb, ${color} ${originalRatio}, ${keyColor} ${colorRatio})`;
},
updateColor() {
updateColor(alpha: number) {
const variableMap = {
'--ytmusic-color-black1': '#212121',
'--ytmusic-color-black2': '#181818',
@ -202,19 +211,20 @@ export default createPlugin<
Object.entries(variableMap).map(([variable, color]) => {
document.documentElement.style.setProperty(
variable,
this.getMixedColor(color, COLOR_KEY),
this.getMixedColor(color, COLOR_KEY, alpha),
'important',
);
});
document.body.style.setProperty(
'background',
this.getMixedColor('#030303', COLOR_KEY),
this.getMixedColor('rgba(3, 3, 3)', DARK_COLOR_KEY, alpha),
'important',
);
document.documentElement.style.setProperty(
'--ytmusic-background',
this.getMixedColor('#030303', DARK_COLOR_KEY),
// #030303
this.getMixedColor('rgba(3, 3, 3)', DARK_COLOR_KEY, alpha),
'important',
);
},

View File

@ -3,7 +3,7 @@ import style from './style.css?inline';
import { t } from '@/i18n';
import { createPlugin } from '@/utils';
import { menu } from './menu';
import { AmbientModePluginConfig } from './types';
import { type AmbientModePluginConfig } from './types';
import { waitForElement } from '@/utils/wait-for-element';
const defaultConfig: AmbientModePluginConfig = {

View File

@ -1,8 +1,8 @@
import { MenuItemConstructorOptions } from 'electron';
import { type MenuItemConstructorOptions } from 'electron';
import { t } from '@/i18n';
import { MenuContext } from '@/types/contexts';
import { AmbientModePluginConfig } from './types';
import { type MenuContext } from '@/types/contexts';
import { type AmbientModePluginConfig } from './types';
export interface menuParameters {
getConfig: () => AmbientModePluginConfig | Promise<AmbientModePluginConfig>;

View File

@ -4,7 +4,7 @@ import { type Context, Hono } from 'hono';
import { cors } from 'hono/cors';
import { serve } from '@hono/node-server';
import registerCallback, { type SongInfo } from '@/providers/song-info';
import { registerCallback, type SongInfo } from '@/providers/song-info';
import { createBackend } from '@/utils';
import type { AmuseSongInfo } from './types';

View File

@ -0,0 +1 @@
export const API_VERSION = 'v1';

View File

@ -3,17 +3,22 @@ import { OpenAPIHono as Hono } from '@hono/zod-openapi';
import { cors } from 'hono/cors';
import { swaggerUI } from '@hono/swagger-ui';
import { serve } from '@hono/node-server';
import { createNodeWebSocket } from '@hono/node-ws';
import registerCallback from '@/providers/song-info';
import { registerCallback } from '@/providers/song-info';
import { createBackend } from '@/utils';
import { JWTPayloadSchema } from './scheme';
import { registerAuth, registerControl } from './routes';
import { registerAuth, registerControl, registerWebsocket } from './routes';
import { type APIServerConfig, AuthStrategy } from '../config';
import type { BackendType } from './types';
import type { RepeatMode } from '@/types/datahost-get-state';
import type {
LikeType,
RepeatMode,
VolumeState,
} from '@/types/datahost-get-state';
export const backend = createBackend<BackendType, APIServerConfig>({
async start(ctx) {
@ -25,9 +30,12 @@ export const backend = createBackend<BackendType, APIServerConfig>({
});
ctx.ipc.on('ytmd:player-api-loaded', () => {
ctx.ipc.send('ytmd:setup-seeked-listener');
ctx.ipc.send('ytmd:setup-time-changed-listener');
ctx.ipc.send('ytmd:setup-repeat-changed-listener');
ctx.ipc.send('ytmd:setup-like-changed-listener');
ctx.ipc.send('ytmd:setup-volume-changed-listener');
ctx.ipc.send('ytmd:setup-shuffle-changed-listener');
});
ctx.ipc.on(
@ -37,7 +45,7 @@ export const backend = createBackend<BackendType, APIServerConfig>({
ctx.ipc.on(
'ytmd:volume-changed',
(newVolume: number) => (this.volume = newVolume),
(newVolumeState: VolumeState) => (this.volumeState = newVolumeState),
);
this.run(config.hostname, config.port);
@ -63,6 +71,10 @@ export const backend = createBackend<BackendType, APIServerConfig>({
init(backendCtx) {
this.app = new Hono();
const ws = createNodeWebSocket({
app: this.app,
});
this.app.use('*', cors());
// for web remote control
@ -103,9 +115,14 @@ export const backend = createBackend<BackendType, APIServerConfig>({
backendCtx,
() => this.songInfo,
() => this.currentRepeatMode,
() => this.volume,
() =>
backendCtx.window.webContents.executeJavaScript(
'document.querySelector("#like-button-renderer")?.likeStatus',
) as Promise<LikeType>,
() => this.volumeState,
);
registerAuth(this.app, backendCtx);
registerWebsocket(this.app, backendCtx, ws);
// swagger
this.app.openAPIRegistry.registerComponent(
@ -133,6 +150,8 @@ export const backend = createBackend<BackendType, APIServerConfig>({
});
this.app.get('/swagger', swaggerUI({ url: '/doc' }));
this.injectWebSocket = ws.injectWebSocket.bind(this);
},
run(hostname, port) {
if (!this.app) return;
@ -143,6 +162,10 @@ export const backend = createBackend<BackendType, APIServerConfig>({
port,
hostname,
});
if (this.injectWebSocket && this.server) {
this.injectWebSocket(this.server);
}
} catch (err) {
console.error(err);
}

View File

@ -1,8 +1,12 @@
import { createRoute, z } from '@hono/zod-openapi';
import { ipcMain } from 'electron';
import getSongControls from '@/providers/song-controls';
import { getSongControls } from '@/providers/song-controls';
import {
LikeType,
type RepeatMode,
type VolumeState,
} from '@/types/datahost-get-state';
import {
AddSongToQueueSchema,
@ -19,8 +23,8 @@ import {
SwitchRepeatSchema,
type ResponseSongInfo,
} from '../scheme';
import { API_VERSION } from '../api-version';
import type { RepeatMode } from '@/types/datahost-get-state';
import type { SongInfo } from '@/providers/song-info';
import type { BackendContext } from '@/types/contexts';
import type { APIServerConfig } from '../../config';
@ -28,8 +32,6 @@ import type { HonoApp } from '../types';
import type { QueueResponse } from '@/types/youtube-music-desktop-internal';
import type { Context } from 'hono';
const API_VERSION = 'v1';
const routes = {
previous: createRoute({
method: 'post',
@ -87,6 +89,24 @@ const routes = {
},
},
}),
getLikeState: createRoute({
method: 'get',
path: `/api/${API_VERSION}/like-state`,
summary: 'get like state',
description: 'Get the current like state',
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: z.object({
state: z.enum(LikeType).nullable(),
}),
},
},
},
},
}),
like: createRoute({
method: 'post',
path: `/api/${API_VERSION}/like`,
@ -274,6 +294,7 @@ const routes = {
'application/json': {
schema: z.object({
state: z.number(),
isMuted: z.boolean(),
}),
},
},
@ -526,12 +547,15 @@ const routes = {
}),
};
type PromiseOrValue<T> = T | Promise<T>;
export const register = (
app: HonoApp,
{ window }: BackendContext<APIServerConfig>,
songInfoGetter: () => SongInfo | undefined,
repeatModeGetter: () => RepeatMode | undefined,
volumeGetter: () => number | undefined,
songInfoGetter: () => PromiseOrValue<SongInfo | undefined>,
repeatModeGetter: () => PromiseOrValue<RepeatMode | undefined>,
likeTypeGetter: () => PromiseOrValue<LikeType | undefined>,
volumeStateGetter: () => PromiseOrValue<VolumeState | undefined>,
) => {
const controller = getSongControls(window);
@ -565,6 +589,10 @@ export const register = (
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.getLikeState, async (ctx) => {
ctx.status(200);
return ctx.json({ state: (await likeTypeGetter()) ?? null });
});
app.openapi(routes.like, (ctx) => {
controller.like();
@ -624,9 +652,9 @@ export const register = (
return ctx.body(null);
});
app.openapi(routes.repeatMode, (ctx) => {
app.openapi(routes.repeatMode, async (ctx) => {
ctx.status(200);
return ctx.json({ mode: repeatModeGetter() ?? null });
return ctx.json({ mode: (await repeatModeGetter()) ?? null });
});
app.openapi(routes.switchRepeat, (ctx) => {
const { iteration } = ctx.req.valid('json');
@ -642,9 +670,11 @@ export const register = (
ctx.status(204);
return ctx.body(null);
});
app.openapi(routes.getVolumeState, (ctx) => {
app.openapi(routes.getVolumeState, async (ctx) => {
ctx.status(200);
return ctx.json({ state: volumeGetter() ?? 0 });
return ctx.json(
(await volumeStateGetter()) ?? { state: 0, isMuted: false },
);
});
app.openapi(routes.setFullscreen, (ctx) => {
const { state } = ctx.req.valid('json');
@ -678,8 +708,8 @@ export const register = (
return ctx.json({ state: fullscreen });
});
const songInfo = (ctx: Context) => {
const info = songInfoGetter();
const songInfo = async (ctx: Context) => {
const info = await songInfoGetter();
if (!info) {
ctx.status(204);

View File

@ -1,2 +1,3 @@
export { register as registerControl } from './control';
export { register as registerAuth } from './auth';
export { register as registerWebsocket } from './websocket';

View File

@ -0,0 +1,154 @@
import { createRoute } from '@hono/zod-openapi';
import { type NodeWebSocket } from '@hono/node-ws';
import {
registerCallback,
type SongInfo,
SongInfoEvent,
} from '@/providers/song-info';
import { API_VERSION } from '../api-version';
import type { WSContext } from 'hono/ws';
import type { Context, Next } from 'hono';
import type { RepeatMode, VolumeState } from '@/types/datahost-get-state';
import type { HonoApp } from '../types';
import type { BackendContext } from '@/types/contexts';
import type { APIServerConfig } from '@/plugins/api-server/config';
enum DataTypes {
PlayerInfo = 'PLAYER_INFO',
VideoChanged = 'VIDEO_CHANGED',
PlayerStateChanged = 'PLAYER_STATE_CHANGED',
PositionChanged = 'POSITION_CHANGED',
VolumeChanged = 'VOLUME_CHANGED',
RepeatChanged = 'REPEAT_CHANGED',
ShuffleChanged = 'SHUFFLE_CHANGED',
}
type PlayerState = {
song?: SongInfo;
isPlaying: boolean;
muted: boolean;
position: number;
volume: number;
repeat: RepeatMode;
shuffle: boolean;
};
export const register = (
app: HonoApp,
{ ipc }: BackendContext<APIServerConfig>,
{ upgradeWebSocket }: NodeWebSocket,
) => {
let volumeState: VolumeState | undefined = undefined;
let repeat: RepeatMode = 'NONE';
let shuffle = false;
let lastSongInfo: SongInfo | undefined = undefined;
const sockets = new Set<WSContext<WebSocket>>();
const send = (type: DataTypes, state: Partial<PlayerState>) => {
sockets.forEach((socket) =>
socket.send(JSON.stringify({ type, ...state })),
);
};
const createPlayerState = ({
songInfo,
volumeState,
repeat,
shuffle,
}: {
songInfo?: SongInfo;
volumeState?: VolumeState;
repeat: RepeatMode;
shuffle: boolean;
}): PlayerState => ({
song: songInfo,
isPlaying: songInfo ? !songInfo.isPaused : false,
muted: volumeState?.isMuted ?? false,
position: songInfo?.elapsedSeconds ?? 0,
volume: volumeState?.state ?? 100,
repeat,
shuffle,
});
registerCallback((songInfo, event) => {
if (event === SongInfoEvent.VideoSrcChanged) {
send(DataTypes.VideoChanged, { song: songInfo, position: 0 });
}
if (event === SongInfoEvent.PlayOrPaused) {
send(DataTypes.PlayerStateChanged, {
isPlaying: !(songInfo?.isPaused ?? true),
position: songInfo.elapsedSeconds,
});
}
if (event === SongInfoEvent.TimeChanged) {
send(DataTypes.PositionChanged, { position: songInfo.elapsedSeconds });
}
lastSongInfo = { ...songInfo };
});
ipc.on('ytmd:volume-changed', (newVolumeState: VolumeState) => {
volumeState = newVolumeState;
send(DataTypes.VolumeChanged, {
volume: volumeState.state,
muted: volumeState.isMuted,
});
});
ipc.on('ytmd:repeat-changed', (mode: RepeatMode) => {
repeat = mode;
send(DataTypes.RepeatChanged, { repeat });
});
ipc.on('ytmd:seeked', (t: number) => {
send(DataTypes.PositionChanged, { position: t });
});
ipc.on('ytmd:shuffle-changed', (newShuffle: boolean) => {
shuffle = newShuffle;
send(DataTypes.ShuffleChanged, { shuffle });
});
app.openapi(
createRoute({
method: 'get',
path: `/api/${API_VERSION}/ws`,
summary: 'websocket endpoint',
description: 'WebSocket endpoint for real-time updates',
responses: {
101: {
description: 'Switching Protocols',
},
},
}),
upgradeWebSocket(() => ({
onOpen(_, ws) {
// "Unsafe argument of type `WSContext<WebSocket>` assigned to a parameter of type `WSContext<WebSocket>`. (@typescript-eslint/no-unsafe-argument)" ????? what?
sockets.add(ws as WSContext<WebSocket>);
ws.send(
JSON.stringify({
type: DataTypes.PlayerInfo,
...createPlayerState({
songInfo: lastSongInfo,
volumeState,
repeat,
shuffle,
}),
}),
);
},
onClose(_, ws) {
sockets.delete(ws as WSContext<WebSocket>);
},
})) as (ctx: Context, next: Next) => Promise<Response>,
);
};

View File

@ -1,9 +1,9 @@
import { OpenAPIHono as Hono } from '@hono/zod-openapi';
import { serve } from '@hono/node-server';
import { type OpenAPIHono as Hono } from '@hono/zod-openapi';
import { type serve } from '@hono/node-server';
import type { RepeatMode, VolumeState } from '@/types/datahost-get-state';
import type { BackendContext } from '@/types/contexts';
import type { SongInfo } from '@/providers/song-info';
import type { RepeatMode } from '@/types/datahost-get-state';
import type { APIServerConfig } from '../config';
export type HonoApp = Hono;
@ -13,7 +13,8 @@ export type BackendType = {
oldConfig?: APIServerConfig;
songInfo?: SongInfo;
currentRepeatMode?: RepeatMode;
volume?: number;
volumeState?: VolumeState;
injectWebSocket?: (server: ReturnType<typeof serve>) => void;
init: (ctx: BackendContext<APIServerConfig>) => void;
run: (hostname: string, port: number) => void;

View File

@ -1,26 +1,133 @@
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
import { type YoutubePlayer } from '@/types/youtube-player';
const lazySafeTry = (...fns: (() => void)[]) => {
for (const fn of fns) {
try {
fn();
} catch {}
}
};
const createCompressorNode = (
audioContext: AudioContext,
): DynamicsCompressorNode => {
const compressor = audioContext.createDynamicsCompressor();
compressor.threshold.value = -50;
compressor.ratio.value = 12;
compressor.knee.value = 40;
compressor.attack.value = 0;
compressor.release.value = 0.25;
return compressor;
};
class Storage {
lastSource: MediaElementAudioSourceNode | null = null;
lastContext: AudioContext | null = null;
lastCompressor: DynamicsCompressorNode | null = null;
connected: WeakMap<MediaElementAudioSourceNode, DynamicsCompressorNode> =
new WeakMap();
connectToCompressor = (
source: MediaElementAudioSourceNode | null = null,
audioContext: AudioContext | null = null,
compressor: DynamicsCompressorNode | null = null,
): boolean => {
if (!(source && audioContext && compressor)) return false;
const current = this.connected.get(source);
if (current === compressor) return false;
this.lastSource = source;
this.lastContext = audioContext;
this.lastCompressor = compressor;
if (current) {
lazySafeTry(
() => source.disconnect(current),
() => current.disconnect(audioContext.destination),
);
} else {
lazySafeTry(() => source.disconnect(audioContext.destination));
}
try {
source.connect(compressor);
compressor.connect(audioContext.destination);
this.connected.set(source, compressor);
return true;
} catch (error) {
console.error('connectToCompressor failed', error);
return false;
}
};
disconnectCompressor = (): boolean => {
const source = this.lastSource;
const audioContext = this.lastContext;
if (!(source && audioContext)) return false;
const current = this.connected.get(source);
if (!current) return false;
lazySafeTry(
() => source.connect(audioContext.destination),
() => source.disconnect(current),
() => current.disconnect(audioContext.destination),
);
this.connected.delete(source);
return true;
};
}
const storage = new Storage();
const audioCanPlayHandler = ({
detail: { audioSource, audioContext },
}: CustomEvent<Compressor>) => {
storage.connectToCompressor(
audioSource,
audioContext,
createCompressorNode(audioContext),
);
};
const ensureAudioContextLoad = (playerApi: YoutubePlayer) => {
if (playerApi.getPlayerState() !== 1 || storage.lastContext) return;
playerApi.loadVideoById(
playerApi.getPlayerResponse().videoDetails.videoId,
playerApi.getCurrentTime(),
playerApi.getUserPlaybackQualityPreference(),
);
};
export default createPlugin({
name: () => t('plugins.audio-compressor.name'),
description: () => t('plugins.audio-compressor.description'),
renderer() {
document.addEventListener(
'ytmd:audio-can-play',
({ detail: { audioSource, audioContext } }) => {
const compressor = audioContext.createDynamicsCompressor();
renderer: {
onPlayerApiReady(playerApi) {
ensureAudioContextLoad(playerApi);
},
compressor.threshold.value = -50;
compressor.ratio.value = 12;
compressor.knee.value = 40;
compressor.attack.value = 0;
compressor.release.value = 0.25;
start() {
document.addEventListener('ytmd:audio-can-play', audioCanPlayHandler, {
passive: true,
});
storage.connectToCompressor(
storage.lastSource,
storage.lastContext,
storage.lastCompressor,
);
},
audioSource.connect(compressor);
compressor.connect(audioContext.destination);
},
{ once: true, passive: true },
);
stop() {
document.removeEventListener('ytmd:audio-can-play', audioCanPlayHandler);
storage.disconnectCompressor();
},
},
});

View File

@ -1,17 +1,15 @@
import net from 'net';
import { SocksClient, SocksClientOptions } from 'socks';
import * as net from 'node:net';
import { SocksClient, type SocksClientOptions } from 'socks';
import is from 'electron-is';
import { createBackend, LoggerPrefix } from '@/utils';
import { BackendType } from './types';
import * as config from '@/config';
import config from '@/config';
import { AuthProxyConfig, defaultAuthProxyConfig } from '../config';
import { type AuthProxyConfig, defaultAuthProxyConfig } from '../config';
import type { BackendType } from './types';
import type { BackendContext } from '@/types/contexts';
// Parsing the upstream authentication SOCK proxy URL

View File

@ -1,5 +1,4 @@
import net from 'net';
import type net from 'net';
import type { AuthProxyConfig } from '../config';
import type { Server } from 'http';

View File

@ -1,13 +0,0 @@
import { inject } from 'simple-youtube-age-restriction-bypass';
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
export default createPlugin({
name: () => t('plugins.bypass-age-restrictions.name'),
description: () => t('plugins.bypass-age-restrictions.description'),
restartNeeded: true,
// See https://github.com/organization/Simple-YouTube-Age-Restriction-Bypass#userscript
renderer: () => inject(),
});

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