Compare commits
392 Commits
v3.6.1
...
refactor/m
| Author | SHA1 | Date | |
|---|---|---|---|
| 507a70015e | |||
| 86c77d141f | |||
| 754ca3caaa | |||
| 61ea104d7b | |||
| 573bdfae03 | |||
| cca493b7d5 | |||
| f47262d27b | |||
| 65bf9129ea | |||
| 87e9b9f7a8 | |||
| 07bc4f05fd | |||
| e3a6808087 | |||
| e9184e5d60 | |||
| a5b32d96f8 | |||
| 040db7539c | |||
| da646c1d53 | |||
| 5f5917f972 | |||
| eccb0d2f08 | |||
| 4cd9dd17df | |||
| 5de07b9a96 | |||
| 151f067beb | |||
| c68a7bd19f | |||
| b87e5e31df | |||
| 03229d61c8 | |||
| b6330eed18 | |||
| b254812ac2 | |||
| 7e243e2fbf | |||
| 307e52cc89 | |||
| f7b7ea916f | |||
| 3eccf8daca | |||
| aa48944212 | |||
| 4d51f1a412 | |||
| d3c9f76582 | |||
| d638a6cf28 | |||
| f93651b219 | |||
| cb8c6c69fe | |||
| 4e7266fb1b | |||
| 8de75ff3a5 | |||
| 3236c88eb2 | |||
| c9f0ad14c2 | |||
| 0a9199c92b | |||
| 3ffcff7d9c | |||
| ddf614d362 | |||
| 6f1a77bbb9 | |||
| 8595f9761e | |||
| cc442182fd | |||
| b827a05eea | |||
| 250abab8bc | |||
| 8e45518ccf | |||
| 7485e065ed | |||
| c1d88f91d4 | |||
| 124a2bd8d0 | |||
| f8f94f9665 | |||
| 5e98a82b23 | |||
| a5c20a66b3 | |||
| cc84116ad1 | |||
| a2e2031708 | |||
| 5001eabf23 | |||
| aac2974430 | |||
| f7f005bb3d | |||
| e1f6d5b7f2 | |||
| b6b607897e | |||
| 651ebb2b1a | |||
| 9fa24deed2 | |||
| c81022d373 | |||
| b726dc7580 | |||
| 471aa7d0a6 | |||
| f34d645ac3 | |||
| d2a11a560e | |||
| 9d185872db | |||
| d0ff71aa66 | |||
| bc8999585f | |||
| 1e1582e31f | |||
| 2c48a0f6f4 | |||
| 368b251e3f | |||
| 3339f997e3 | |||
| 76e8e7aa7a | |||
| 8c325b17f8 | |||
| 494bc0ccc7 | |||
| f14333d07a | |||
| 268ffe2d4c | |||
| 831eb63ace | |||
| 345f235117 | |||
| c6422d5d9c | |||
| f90384386c | |||
| 9213fdd4f7 | |||
| 787c8cdceb | |||
| ace48bc79b | |||
| e5a8b7431f | |||
| e37f22503b | |||
| e61757a7fc | |||
| fdc798ad87 | |||
| 6393e6348c | |||
| ce6e115783 | |||
| 68cbaabc0c | |||
| 9911bbf509 | |||
| 553c2c1096 | |||
| dde57e3eb0 | |||
| 935461f610 | |||
| b95da863c6 | |||
| 92316a999c | |||
| 0ec038342c | |||
| 4649c3bfaf | |||
| 8441af2483 | |||
| 63c4a9cfa7 | |||
| 4e8173360e | |||
| 4f37377d64 | |||
| 0e886c0890 | |||
| cb0719ecf4 | |||
| d24b31cf74 | |||
| 610073b982 | |||
| 493b65bb11 | |||
| e4e7ef3b52 | |||
| e1dc19e9eb | |||
| 4b9f92ed2d | |||
| 622beccd95 | |||
| 8678fca9c0 | |||
| 4dcb9b5995 | |||
| 718025445c | |||
| 845dac3c03 | |||
| a1ac3d1359 | |||
| c21dd08a40 | |||
| 8de27358d3 | |||
| c4910af494 | |||
| 67fc0a415c | |||
| 7d145cbca1 | |||
| a565d9fc0f | |||
| 71a9ed5d65 | |||
| fc16a325f7 | |||
| c52d96dcbd | |||
| d2c681a047 | |||
| 8e75df42bf | |||
| 85d1dc46d1 | |||
| 84f5e265f2 | |||
| 0d261a8e44 | |||
| 3c228ede48 | |||
| 8b971d2263 | |||
| 6b8fed3fc2 | |||
| f15c51f3d7 | |||
| fa3e146ef7 | |||
| 4baec1560c | |||
| 0e0230b995 | |||
| a33a03f512 | |||
| f8a53f0d61 | |||
| 748d77d1c0 | |||
| 725ad0d630 | |||
| bdbab17772 | |||
| 57d2fa372d | |||
| 80471b0ca4 | |||
| 22fdfe3342 | |||
| 5ecfa2a1f7 | |||
| b9beea810e | |||
| f0e77812e7 | |||
| 6d1237c2a2 | |||
| b43c92386e | |||
| 017476a81b | |||
| 9b047d9c54 | |||
| 31f009d3c4 | |||
| 8504f2c086 | |||
| 1d6251baea | |||
| 3ea13a2a22 | |||
| 1cc153084d | |||
| 1c468b4054 | |||
| 1bad46890a | |||
| 5829c8d0f7 | |||
| 85aceaaae4 | |||
| 24e593b22f | |||
| 3f8ca6002e | |||
| b62ccfe7b1 | |||
| 237dde9765 | |||
| 65f4339fd1 | |||
| 109e9f8166 | |||
| 9163b6f04b | |||
| 51da259c97 | |||
| 2bf67b941e | |||
| 533b96d1f6 | |||
| 5c9ded8779 | |||
| 6f389bb297 | |||
| 8a209404d4 | |||
| 6193fb487a | |||
| 9aa7f7a023 | |||
| 5bfaa9a791 | |||
| d210ec8227 | |||
| dec7c5e95c | |||
| 940d0beb84 | |||
| cf98754276 | |||
| d91d493dd1 | |||
| 7e1aea21db | |||
| 0179dfd311 | |||
| 98ea26bbff | |||
| 0d9daaad66 | |||
| fe319daec1 | |||
| 929c58671a | |||
| 4fb2350c2b | |||
| a401bfa809 | |||
| fdeed76f6f | |||
| 0ab113816a | |||
| 8a58b02c7b | |||
| 037b059b55 | |||
| bb0f9fb3d0 | |||
| d3c7848896 | |||
| ea50cb1e65 | |||
| 5070fd88b5 | |||
| 21177478cb | |||
| 26b8b38b89 | |||
| be04d66aa8 | |||
| a837987e70 | |||
| da99558163 | |||
| 3b50cbcb6e | |||
| 595c011bce | |||
| 458fe54063 | |||
| ae3a289005 | |||
| a49eea9246 | |||
| d675a175e9 | |||
| 6c510a71c2 | |||
| 5503d2cbb8 | |||
| ba4c7e1a0c | |||
| e19c458441 | |||
| ec5cf0cae8 | |||
| b3c4570f8c | |||
| 112b6d893b | |||
| 52236907e4 | |||
| d449529ea7 | |||
| 61c799f7d4 | |||
| 1a4ee13e47 | |||
| f91afb984a | |||
| f9892b0eae | |||
| 60c7885a3c | |||
| 63ca6aa533 | |||
| e77a8c04e8 | |||
| 0bfabf604c | |||
| 4343c599cf | |||
| c251554c31 | |||
| 95e519bdc9 | |||
| 79d38bfc8e | |||
| f5655b0ae6 | |||
| 12b4afc3ce | |||
| c104d47737 | |||
| 870cf6143c | |||
| 1baed0e913 | |||
| 02619c79bb | |||
| 0faad538f3 | |||
| e7de30c629 | |||
| c5d8333039 | |||
| 4da08e7c9b | |||
| 171387995a | |||
| aeac020c9a | |||
| 5c05ddeb29 | |||
| cbdd649365 | |||
| d2cf2ad71f | |||
| 7d33494097 | |||
| ca83edabf3 | |||
| f84e77e814 | |||
| 14f2120a32 | |||
| 1cbf14ee2a | |||
| b7cb167fc6 | |||
| 41b9f8b967 | |||
| 0b769ce287 | |||
| ad71ef8a68 | |||
| 048a994f32 | |||
| c2bd8ce188 | |||
| 62ce4e818c | |||
| 4ab8829a02 | |||
| 36b3e2cb0c | |||
| 9ba0614a7d | |||
| 81431ad196 | |||
| a8e8d5afd7 | |||
| f3d86743ee | |||
| 6306968193 | |||
| 7142a253d6 | |||
| 2a24588338 | |||
| 2abaf54ac8 | |||
| cc730ad55c | |||
| d8581c5d69 | |||
| 3208bf4a6d | |||
| 4109db1ad7 | |||
| 87a0ef5d54 | |||
| cdc40f0c53 | |||
| 7a3b8082a2 | |||
| 3e9039c97d | |||
| b9d1130468 | |||
| 605f0984e4 | |||
| 44de7d9e98 | |||
| 9926575744 | |||
| f16a99f6e4 | |||
| a23c64b5b8 | |||
| 0899f76548 | |||
| 515dcdc7e3 | |||
| b2c4bc425b | |||
| 363c3b3a67 | |||
| c2dde3d78f | |||
| eb515cfc61 | |||
| c208ca184f | |||
| c231fa7c44 | |||
| 9e1b8d43d0 | |||
| f50ece88df | |||
| eeb780d190 | |||
| cafdf654d3 | |||
| 2d665013e7 | |||
| 451a46e208 | |||
| 490b901c34 | |||
| b57b4a3454 | |||
| 60c61e32b1 | |||
| aa9052d449 | |||
| 67f3a38583 | |||
| a00ecc4729 | |||
| 56d63fca52 | |||
| 759f3ba317 | |||
| d8daf03f2c | |||
| a519c7c714 | |||
| 933d12fdd1 | |||
| 61fb733550 | |||
| 7f05e3168d | |||
| 3b7697a90d | |||
| 350b1467fe | |||
| 2f5d102f4d | |||
| 66e296df1a | |||
| 1e4cd699db | |||
| 516fbff3d7 | |||
| aab9358d67 | |||
| ae3939f857 | |||
| 79bafd1780 | |||
| 3f4f52a31f | |||
| c2b7b29716 | |||
| dab84b9cf9 | |||
| e5980158eb | |||
| 647d4c9d99 | |||
| 30feb6128b | |||
| 0cf6923540 | |||
| cce9f0b462 | |||
| c0805fb758 | |||
| 04e5844301 | |||
| f28e663133 | |||
| 2c84527c43 | |||
| 68511de727 | |||
| e7ca9f129f | |||
| 259da70e4f | |||
| 9409d75ac7 | |||
| 3ea923f56f | |||
| 6d6c8c94cf | |||
| 29098758a9 | |||
| fdbb35e221 | |||
| 6ddac62313 | |||
| ae4b494300 | |||
| 7d9eed88f4 | |||
| 4abf848f99 | |||
| cc0a6cfdce | |||
| a74d0dd0ca | |||
| 18f15d4cce | |||
| 80e20c6579 | |||
| 761026fd74 | |||
| 95b75f020c | |||
| 574e4baef8 | |||
| ec4871d5a8 | |||
| ccd6bf9c3f | |||
| 2975d4292c | |||
| ad9571550f | |||
| 980068217c | |||
| 18f69aea3f | |||
| add7cb9e48 | |||
| aa67a57971 | |||
| 74f22b4474 | |||
| 7ef4a23576 | |||
| 3c90a1f459 | |||
| 3793d36f36 | |||
| 179f4b29db | |||
| 34f106896c | |||
| 06b581f499 | |||
| 3ec126628c | |||
| 1d3bb60e0b | |||
| 2e3ced6006 | |||
| e8efca5a3e | |||
| 5161c356f9 | |||
| e917b30e64 | |||
| 2d847f9808 | |||
| 21755fffc7 | |||
| 5c9d3e3e67 | |||
| ea801f65ef | |||
| 27f4c0393e | |||
| 9bc42f836f | |||
| 11b11ed966 | |||
| 5f79b7e788 | |||
| 7d1d806797 | |||
| 836cedb0f3 | |||
| 70349e13cc | |||
| 6d16b74471 | |||
| c211780c33 | |||
| 2173ba0234 | |||
| 3a4cbc543b | |||
| 2fef7f0246 | |||
| 12d693921e | |||
| d1b4879f51 | |||
| f14939fcd2 |
10
.github/workflows/build.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
version: 10
|
||||
run_install: false
|
||||
|
||||
- name: Setup NodeJS
|
||||
@ -65,9 +65,9 @@ jobs:
|
||||
sudo apt update
|
||||
sudo apt install -y flatpak flatpak-builder
|
||||
sudo flatpak remote-add --if-not-exists --system flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
sudo flatpak install -y flathub org.freedesktop.Platform/x86_64/20.08
|
||||
sudo flatpak install -y flathub org.freedesktop.Sdk/x86_64/20.08
|
||||
sudo flatpak install -y flathub org.electronjs.Electron2.BaseApp/x86_64/20.08
|
||||
sudo flatpak install -y flathub org.freedesktop.Platform/x86_64/24.08
|
||||
sudo flatpak install -y flathub org.freedesktop.Sdk/x86_64/24.08
|
||||
sudo flatpak install -y flathub org.electronjs.Electron2.BaseApp/x86_64/24.08
|
||||
pnpm release:linux
|
||||
|
||||
- name: Build and release on Windows
|
||||
@ -98,7 +98,7 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
version: 10
|
||||
run_install: false
|
||||
|
||||
- name: Setup NodeJS
|
||||
|
||||
10
README.md
@ -12,7 +12,7 @@
|
||||
|
||||
</div>
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
<div align="center">
|
||||
@ -21,7 +21,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸](./docs/readme/README-is.md), [🇨🇱 🇪🇸](./docs/readme/README-es.md), [🇷🇺](./docs/readme/README-ru.md)
|
||||
Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸](./docs/readme/README-is.md), [🇨🇱 🇪🇸](./docs/readme/README-es.md), [🇷🇺](./docs/readme/README-ru.md), [🇭🇺](./docs/readme/README-hu.md)
|
||||
|
||||
**Electron wrapper around YouTube Music featuring:**
|
||||
|
||||
@ -67,7 +67,7 @@ Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸]
|
||||
## 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
|
||||
@ -95,6 +95,8 @@ Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸]
|
||||
- **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
|
||||
@ -192,7 +194,7 @@ brew install th-ch/youtube-music/youtube-music
|
||||
If you install the app manually and get an error "is damaged and can’t be opened." when launching the app, run the following in the Terminal:
|
||||
|
||||
```bash
|
||||
xattr -cr /Applications/YouTube\ Music.app
|
||||
/usr/bin/xattr -cr /Applications/YouTube\ Music.app
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
|
Before Width: | Height: | Size: 250 B After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 265 B After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 13 KiB |
306
changelog.md
@ -2,8 +2,314 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
|
||||
|
||||
#### [v3.7.3](https://github.com/th-ch/youtube-music/compare/v3.7.2...v3.7.3)
|
||||
|
||||
- fix(downloader): use the upgrade button to check for premium status [`#2987`](https://github.com/th-ch/youtube-music/pull/2987)
|
||||
- chore(deps): update dependency electron-vite to v3 [`#2986`](https://github.com/th-ch/youtube-music/pull/2986)
|
||||
- chore(deps): update dependency @babel/runtime to v7.26.9 [`#2980`](https://github.com/th-ch/youtube-music/pull/2980)
|
||||
- fix(vite): set server.cors.origin [`#2981`](https://github.com/th-ch/youtube-music/pull/2981)
|
||||
- chore(deps-dev): bump esbuild from 0.24.2 to 0.25.0 [`#2973`](https://github.com/th-ch/youtube-music/pull/2973)
|
||||
- fix(deps): update dependency solid-transition-group to v0.3.0 [`#2949`](https://github.com/th-ch/youtube-music/pull/2949)
|
||||
- fix: remove disable-gpu-memory-buffer-video-frames flag [`#2963`](https://github.com/th-ch/youtube-music/pull/2963)
|
||||
- fix(deps): update dependency semver to v7.7.0 [`#2948`](https://github.com/th-ch/youtube-music/pull/2948)
|
||||
- chore(deps): update playwright monorepo to v1.50.1 [`#2943`](https://github.com/th-ch/youtube-music/pull/2943)
|
||||
- fix(deps): update dependency @hono/node-server to v1.13.8 [`#2944`](https://github.com/th-ch/youtube-music/pull/2944)
|
||||
- fix(deps): update dependency electron-store to v10.0.1 [`#2945`](https://github.com/th-ch/youtube-music/pull/2945)
|
||||
- chore(deps): update dependency rollup to v4.34.1 [`#2946`](https://github.com/th-ch/youtube-music/pull/2946)
|
||||
- chore(deps): update dependency typescript-eslint to v8.22.0 [`#2947`](https://github.com/th-ch/youtube-music/pull/2947)
|
||||
- fix(synced-lyrics): Fix reverse direction of synced lyrics for persian or other rtl languages [`#2940`](https://github.com/th-ch/youtube-music/pull/2940)
|
||||
- chore(deps): update dependency electron to v34.0.2 [`#2942`](https://github.com/th-ch/youtube-music/pull/2942)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.119 [`#2941`](https://github.com/th-ch/youtube-music/pull/2941)
|
||||
- fix(deps): update dependency hono to v4.6.20 [`#2932`](https://github.com/th-ch/youtube-music/pull/2932)
|
||||
- chore(deps): update eslint monorepo to v9.19.0 [`#2935`](https://github.com/th-ch/youtube-music/pull/2935)
|
||||
- fix(deps): update dependency bgutils-js to v3.1.3 [`#2934`](https://github.com/th-ch/youtube-music/pull/2934)
|
||||
- fix(deps): update dependency i18next to v24.2.2 [`#2933`](https://github.com/th-ch/youtube-music/pull/2933)
|
||||
- fix(deps): update dependency happy-dom to v16.8.1 [`#2936`](https://github.com/th-ch/youtube-music/pull/2936)
|
||||
- chore(deps): update dependency @babel/runtime to v7.26.7 [`#2924`](https://github.com/th-ch/youtube-music/pull/2924)
|
||||
- chore(config): migrate renovate config [`#2925`](https://github.com/th-ch/youtube-music/pull/2925)
|
||||
- fix(deps): update dependency @ghostery/adblocker-electron-preload to v2.5.0 [`#2923`](https://github.com/th-ch/youtube-music/pull/2923)
|
||||
- fix(deps): update dependency @ghostery/adblocker-electron to v2.5.0 [`#2922`](https://github.com/th-ch/youtube-music/pull/2922)
|
||||
- chore(deps): update playwright monorepo to v1.50.0 [`#2921`](https://github.com/th-ch/youtube-music/pull/2921)
|
||||
- chore(deps): update dependency vite-plugin-inspect to v10.1.0 [`#2920`](https://github.com/th-ch/youtube-music/pull/2920)
|
||||
- chore(deps): update dependency rollup to v4.32.0 [`#2919`](https://github.com/th-ch/youtube-music/pull/2919)
|
||||
- fix(deps): update dependency hono to v4.6.18 [`#2918`](https://github.com/th-ch/youtube-music/pull/2918)
|
||||
- fix(deps): update dependency deepmerge-ts to v7.1.4 [`#2917`](https://github.com/th-ch/youtube-music/pull/2917)
|
||||
- chore(deps): update dependency vite to v6.0.11 [`#2894`](https://github.com/th-ch/youtube-music/pull/2894)
|
||||
- chore(deps): update dependency electron to v34.0.1 [`#2916`](https://github.com/th-ch/youtube-music/pull/2916)
|
||||
- chore(deps): update dependency electron-builder-squirrel-windows to v26.0.0-alpha.10 [`#2899`](https://github.com/th-ch/youtube-music/pull/2899)
|
||||
- chore(deps): update dependency electron-builder to v26.0.0-alpha.10 [`#2898`](https://github.com/th-ch/youtube-music/pull/2898)
|
||||
- chore(deps): update dependency typescript-eslint to v8.21.0 [`#2901`](https://github.com/th-ch/youtube-music/pull/2901)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.117 [`#2895`](https://github.com/th-ch/youtube-music/pull/2895)
|
||||
- fix(deps): update dependency youtubei.js to v13 [`#2904`](https://github.com/th-ch/youtube-music/pull/2904)
|
||||
- chore(deps): update dependency vite to v6.0.9 [security] [`#2907`](https://github.com/th-ch/youtube-music/pull/2907)
|
||||
- fix(deps): update dependency happy-dom to v16.7.2 [`#2902`](https://github.com/th-ch/youtube-music/pull/2902)
|
||||
- fix(discord-plugin): handle album name padding if length < 2 [`#2903`](https://github.com/th-ch/youtube-music/pull/2903)
|
||||
- feat(navigation): added nav icon padding [`#2905`](https://github.com/th-ch/youtube-music/pull/2905)
|
||||
- chore(deps): update dependency rollup to v4.31.0 [`#2891`](https://github.com/th-ch/youtube-music/pull/2891)
|
||||
- chore(deps): update dependency eslint-plugin-prettier to v5.2.3 [`#2889`](https://github.com/th-ch/youtube-music/pull/2889)
|
||||
- chore(deps): update dependency vite-plugin-inspect to v10.0.7 [`#2882`](https://github.com/th-ch/youtube-music/pull/2882)
|
||||
- fix(deps): update dependency hono to v4.6.17 [`#2883`](https://github.com/th-ch/youtube-music/pull/2883)
|
||||
- fix: bump deps [`e9184e5`](https://github.com/th-ch/youtube-music/commit/e9184e5d60c2495473a7c3226ce9748ba89fceb3)
|
||||
- fix(deps): fix pnpm [`040db75`](https://github.com/th-ch/youtube-music/commit/040db7539ccd1ae40f2632fdf38168cdaa26f112)
|
||||
- chore(i18n): Translated using Weblate (Persian) [`9d18587`](https://github.com/th-ch/youtube-music/commit/9d185872dba5b56dabc691e56eafb13dc192b9cd)
|
||||
|
||||
#### [v3.7.2](https://github.com/th-ch/youtube-music/compare/v3.7.1...v3.7.2)
|
||||
|
||||
> 18 January 2025
|
||||
|
||||
- feat(api-server): add endpoint to get shuffle state [`#2792`](https://github.com/th-ch/youtube-music/pull/2792)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.116 [`#2877`](https://github.com/th-ch/youtube-music/pull/2877)
|
||||
- chore(deps): update dependency eslint-plugin-prettier to v5.2.2 [`#2875`](https://github.com/th-ch/youtube-music/pull/2875)
|
||||
- chore(deps): update eslint monorepo to v9.18.0 [`#2858`](https://github.com/th-ch/youtube-music/pull/2858)
|
||||
- chore(deps): update dependency glob to v11.0.1 [`#2857`](https://github.com/th-ch/youtube-music/pull/2857)
|
||||
- chore(deps): update dependency electron-builder-squirrel-windows to v26.0.0-alpha.9 [`#2874`](https://github.com/th-ch/youtube-music/pull/2874)
|
||||
- chore(deps): update dependency electron to v34 [`#2867`](https://github.com/th-ch/youtube-music/pull/2867)
|
||||
- chore(deps): update dependency eslint-config-prettier to v10 [`#2866`](https://github.com/th-ch/youtube-music/pull/2866)
|
||||
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.13.0 [`#2864`](https://github.com/th-ch/youtube-music/pull/2864)
|
||||
- chore(deps): update dependency typescript-eslint to v8.20.0 [`#2865`](https://github.com/th-ch/youtube-music/pull/2865)
|
||||
- chore(deps): update dependency electron-builder to v26.0.0-alpha.9 [`#2869`](https://github.com/th-ch/youtube-music/pull/2869)
|
||||
- fix: fix build.linux.desktop.entry [`#2859`](https://github.com/th-ch/youtube-music/pull/2859)
|
||||
- feat(api-server): add endpoint to get volume state [`#2813`](https://github.com/th-ch/youtube-music/pull/2813)
|
||||
- chore(deps): update dependency vite-plugin-inspect to v10 [`#2856`](https://github.com/th-ch/youtube-music/pull/2856)
|
||||
- chore(deps): update dependency typescript to v5.7.3 [`#2855`](https://github.com/th-ch/youtube-music/pull/2855)
|
||||
- fix(deps): update dependency @floating-ui/dom to v1.6.13 [`#2846`](https://github.com/th-ch/youtube-music/pull/2846)
|
||||
- chore(deps): bump nanoid from 3.3.7 to 3.3.8 [`#2854`](https://github.com/th-ch/youtube-music/pull/2854)
|
||||
- chore(deps): update dependency electron to v33.3.1 [`#2841`](https://github.com/th-ch/youtube-music/pull/2841)
|
||||
- fix(deps): update dependency i18next to v24.2.1 [`#2840`](https://github.com/th-ch/youtube-music/pull/2840)
|
||||
- chore(deps): update dependency typescript-eslint to v8.19.1 [`#2836`](https://github.com/th-ch/youtube-music/pull/2836)
|
||||
- chore(deps): update dependency rollup to v4.30.1 [`#2833`](https://github.com/th-ch/youtube-music/pull/2833)
|
||||
- fix(deps): update dependency solid-js to v1.9.4 [`#2849`](https://github.com/th-ch/youtube-music/pull/2849)
|
||||
- fix(deps): update dependency fast-equals to v5.2.2 [`#2842`](https://github.com/th-ch/youtube-music/pull/2842)
|
||||
- chore: Update README.md [`#2845`](https://github.com/th-ch/youtube-music/pull/2845)
|
||||
- chore: Fixing the Content section in the README-ru.md file. [`#2847`](https://github.com/th-ch/youtube-music/pull/2847)
|
||||
- chore: Create youtube-music-hu.svg [`#2844`](https://github.com/th-ch/youtube-music/pull/2844)
|
||||
- chore: Create Transalated README-hu.md [`#2843`](https://github.com/th-ch/youtube-music/pull/2843)
|
||||
- chore(deps): update dependency vite to v6.0.7 [`#2819`](https://github.com/th-ch/youtube-music/pull/2819)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.115 [`#2818`](https://github.com/th-ch/youtube-music/pull/2818)
|
||||
- fix(deps): update dependency hono to v4.6.16 [`#2829`](https://github.com/th-ch/youtube-music/pull/2829)
|
||||
- chore(deps): update dependency rollup to v4.29.2 [`#2832`](https://github.com/th-ch/youtube-music/pull/2832)
|
||||
- fix(deps): update dependency fast-equals to v5.2.0 [`#2822`](https://github.com/th-ch/youtube-music/pull/2822)
|
||||
- feat(api-server): add `insertPosition` for `addSongToQueue` [`#2808`](https://github.com/th-ch/youtube-music/pull/2808)
|
||||
- chore(deps): update dependency typescript-eslint to v8.19.0 [`#2812`](https://github.com/th-ch/youtube-music/pull/2812)
|
||||
- fix(deps): update dependency ts-morph to v25 [`#2810`](https://github.com/th-ch/youtube-music/pull/2810)
|
||||
- fix(renderer): update event handler from onVolumeTap to onVolumeClick [`#2791`](https://github.com/th-ch/youtube-music/pull/2791)
|
||||
- fix(deps): update dependency hono to v4.6.15 [`#2796`](https://github.com/th-ch/youtube-music/pull/2796)
|
||||
- chore(deps): update dependency bufferutil to v4.0.9 [`#2787`](https://github.com/th-ch/youtube-music/pull/2787)
|
||||
- feat: Refactor Menu Navigation and Update Media Control Icons [`#2783`](https://github.com/th-ch/youtube-music/pull/2783)
|
||||
- fix(synced-lyrics): Revert font-size behavior for non-fancy modes [`#2788`](https://github.com/th-ch/youtube-music/pull/2788)
|
||||
- fix(downloader): apply poToken [`#2863`](https://github.com/th-ch/youtube-music/issues/2863) [`#2780`](https://github.com/th-ch/youtube-music/issues/2780)
|
||||
- chore(deps): update dependency electron-builder to v26 [`67fc0a4`](https://github.com/th-ch/youtube-music/commit/67fc0a415cae231a11f2846aadf01edb04f5c677)
|
||||
- fix: fix lock file [`3339f99`](https://github.com/th-ch/youtube-music/commit/3339f997e3c2d4d2c32b3aee95c65d561f123fcb)
|
||||
- chore(i18n): Translated using Weblate (Romanian) [`845dac3`](https://github.com/th-ch/youtube-music/commit/845dac3c0393dadea8efdd03ba1f41b1b36e6191)
|
||||
|
||||
#### [v3.7.1](https://github.com/th-ch/youtube-music/compare/v3.7.0...v3.7.1)
|
||||
|
||||
> 27 December 2024
|
||||
|
||||
- fix(deps): update dependency node-html-parser to v7 [`#2776`](https://github.com/th-ch/youtube-music/pull/2776)
|
||||
- chore(deps): update dependency vite to v6.0.6 [`#2774`](https://github.com/th-ch/youtube-music/pull/2774)
|
||||
- feat(api-server): Add queue api [`#2767`](https://github.com/th-ch/youtube-music/pull/2767)
|
||||
- fix(downloader): fix #2234 [`#2234`](https://github.com/th-ch/youtube-music/issues/2234)
|
||||
- fix(downloader): fix #2769 [`#2769`](https://github.com/th-ch/youtube-music/issues/2769)
|
||||
- fix: fix #2645, fix #2741 [`#2645`](https://github.com/th-ch/youtube-music/issues/2645) [`#2741`](https://github.com/th-ch/youtube-music/issues/2741)
|
||||
- Update changelog for v3.7.0 [`1cc1530`](https://github.com/th-ch/youtube-music/commit/1cc153084d5f354ea928fcde50207f8f6b14ea4c)
|
||||
- fix: use networkManager.fetch instead of fetch [`80471b0`](https://github.com/th-ch/youtube-music/commit/80471b0ca4b3d3efc9e3db87d434290c9ebc79f6)
|
||||
- chore(i18n): Translated using Weblate (Hindi) [`6d1237c`](https://github.com/th-ch/youtube-music/commit/6d1237c2a2ad2408a738e00992ae5ed8a1e900da)
|
||||
|
||||
#### [v3.7.0](https://github.com/th-ch/youtube-music/compare/v3.6.2...v3.7.0)
|
||||
|
||||
> 25 December 2024
|
||||
|
||||
- feat(amuse): song query api (add amuse plugin) [`#2723`](https://github.com/th-ch/youtube-music/pull/2723)
|
||||
- feat(api-server): add absolute seek endpoint [`#2748`](https://github.com/th-ch/youtube-music/pull/2748)
|
||||
- feat(api-server): Add repeat mode and seek time API [`#2630`](https://github.com/th-ch/youtube-music/pull/2630)
|
||||
- feat(synced-lyrics): Better-Lyrics Styling for Synced-Lyrics [`#2554`](https://github.com/th-ch/youtube-music/pull/2554)
|
||||
- feat(synced-lyrics): multiple lyric sources [`#2383`](https://github.com/th-ch/youtube-music/pull/2383)
|
||||
- chore(deps): update dependency typescript-eslint to v8.18.2 [`#2763`](https://github.com/th-ch/youtube-music/pull/2763)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.114 [`#2761`](https://github.com/th-ch/youtube-music/pull/2761)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.113 [`#2759`](https://github.com/th-ch/youtube-music/pull/2759)
|
||||
- fix: Set correct window class for X11 and Wayland [`#2758`](https://github.com/th-ch/youtube-music/pull/2758)
|
||||
- feat: Specify flatpak runtime [`#2755`](https://github.com/th-ch/youtube-music/pull/2755)
|
||||
- chore(deps): update dependency rollup to v4.29.1 [`#2749`](https://github.com/th-ch/youtube-music/pull/2749)
|
||||
- chore(deps): update dependency esbuild to v0.24.2 [`#2742`](https://github.com/th-ch/youtube-music/pull/2742)
|
||||
- fix: Add Flatpak permissions needed for MPRIS and tray icon [`#2754`](https://github.com/th-ch/youtube-music/pull/2754)
|
||||
- chore(deps): update dependency vite-plugin-inspect to v0.10.6 [`#2756`](https://github.com/th-ch/youtube-music/pull/2756)
|
||||
- chore(deps): update dependency vite to v6.0.5 [`#2745`](https://github.com/th-ch/youtube-music/pull/2745)
|
||||
- fix(deps): update dependency i18next to v24.2.0 [`#2744`](https://github.com/th-ch/youtube-music/pull/2744)
|
||||
- chore(deps): update dependency vite-plugin-inspect to v0.10.4 [`#2743`](https://github.com/th-ch/youtube-music/pull/2743)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.112 [`#2740`](https://github.com/th-ch/youtube-music/pull/2740)
|
||||
- fix(discord): Fix Album Art failing on Discord RPC [`#2666`](https://github.com/th-ch/youtube-music/pull/2666)
|
||||
- feat: Add equalizer plugin with presets (e.g. bass booster) [`#2575`](https://github.com/th-ch/youtube-music/pull/2575)
|
||||
- chore(deps): update dependency vite to v6.0.4 [`#2738`](https://github.com/th-ch/youtube-music/pull/2738)
|
||||
- fix: Fixed #1796 [`#2736`](https://github.com/th-ch/youtube-music/pull/2736)
|
||||
- chore(deps): update dependency electron-devtools-installer to v4 [`#2734`](https://github.com/th-ch/youtube-music/pull/2734)
|
||||
- Revert "chore(deps): update dependency electron-builder to v25" [`#2732`](https://github.com/th-ch/youtube-music/pull/2732)
|
||||
- chore(deps): update dependency electron-builder to v25 [`#2490`](https://github.com/th-ch/youtube-music/pull/2490)
|
||||
- fix(deps): update dependency i18next to v24.1.2 [`#2727`](https://github.com/th-ch/youtube-music/pull/2727)
|
||||
- chore(deps): update dependency electron-devtools-installer to v3.2.1 [`#2731`](https://github.com/th-ch/youtube-music/pull/2731)
|
||||
- chore(deps): update dependency typescript-eslint to v8.18.1 [`#2724`](https://github.com/th-ch/youtube-music/pull/2724)
|
||||
- fix: tab misalignment [`#2713`](https://github.com/th-ch/youtube-music/pull/2713)
|
||||
- fix(deps): update dependency @hono/zod-validator to v0.4.2 [`#2709`](https://github.com/th-ch/youtube-music/pull/2709)
|
||||
- chore(deps): update eslint monorepo to v9.17.0 [`#2712`](https://github.com/th-ch/youtube-music/pull/2712)
|
||||
- fix(deps): update dependency hono to v4.6.14 [`#2716`](https://github.com/th-ch/youtube-music/pull/2716)
|
||||
- fix: discord rich presence connection status [`#2714`](https://github.com/th-ch/youtube-music/pull/2714)
|
||||
- fix: Laggy scrolling behaviour in large playlists [`#2708`](https://github.com/th-ch/youtube-music/pull/2708)
|
||||
- fix(deps): update dependency youtubei.js to v12.2.0 [`#2705`](https://github.com/th-ch/youtube-music/pull/2705)
|
||||
- fix(deps): update dependency i18next to v24.1.0 [`#2698`](https://github.com/th-ch/youtube-music/pull/2698)
|
||||
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.12.1 [`#2697`](https://github.com/th-ch/youtube-music/pull/2697)
|
||||
- fix(deps): update dependency zod to v3.24.1 [`#2694`](https://github.com/th-ch/youtube-music/pull/2694)
|
||||
- fix(deps): update dependency youtubei.js to v12.1.0 [`#2695`](https://github.com/th-ch/youtube-music/pull/2695)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.111 [`#2690`](https://github.com/th-ch/youtube-music/pull/2690)
|
||||
- chore(deps): update dependency typescript-eslint to v8.18.0 [`#2692`](https://github.com/th-ch/youtube-music/pull/2692)
|
||||
- chore(deps): update playwright monorepo to v1.49.1 [`#2693`](https://github.com/th-ch/youtube-music/pull/2693)
|
||||
- fix(deps): update dependency hono to v4.6.13 [`#2682`](https://github.com/th-ch/youtube-music/pull/2682)
|
||||
- chore(deps): update dependency rollup to v4.28.1 [`#2683`](https://github.com/th-ch/youtube-music/pull/2683)
|
||||
- fix(deps): update dependency conf to v13.1.0 [`#2686`](https://github.com/th-ch/youtube-music/pull/2686)
|
||||
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.12.0 [`#2689`](https://github.com/th-ch/youtube-music/pull/2689)
|
||||
- fix(deps): update dependency youtubei.js to v12 [`#2681`](https://github.com/th-ch/youtube-music/pull/2681)
|
||||
- chore(deps): update dependency vite to v6.0.3 [`#2680`](https://github.com/th-ch/youtube-music/pull/2680)
|
||||
- fix(album-actions): Fixed #2312 [`#2676`](https://github.com/th-ch/youtube-music/pull/2676)
|
||||
- chore(deps): update dependency eslint-import-resolver-typescript to v3.7.0 [`#2672`](https://github.com/th-ch/youtube-music/pull/2672)
|
||||
- chore(deps): update dependency node-gyp to v11 [`#2678`](https://github.com/th-ch/youtube-music/pull/2678)
|
||||
- fix(deps): update dependency i18next to v24.0.5 [`#2669`](https://github.com/th-ch/youtube-music/pull/2669)
|
||||
- fix(deps): update dependency i18next to v24.0.4 [`#2668`](https://github.com/th-ch/youtube-music/pull/2668)
|
||||
- chore(deps): update dependency vite to v6.0.2 [`#2662`](https://github.com/th-ch/youtube-music/pull/2662)
|
||||
- chore(deps): update dependency node-gyp to v10.3.1 [`#2665`](https://github.com/th-ch/youtube-music/pull/2665)
|
||||
- chore(deps): update dependency typescript-eslint to v8.17.0 [`#2664`](https://github.com/th-ch/youtube-music/pull/2664)
|
||||
- chore(deps): update dependency vite-plugin-inspect to v0.10.3 [`#2667`](https://github.com/th-ch/youtube-music/pull/2667)
|
||||
- chore(deps): update dependency rollup to v4.28.0 [`#2661`](https://github.com/th-ch/youtube-music/pull/2661)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.110 [`#2653`](https://github.com/th-ch/youtube-music/pull/2653)
|
||||
- fix(deps): update dependency @hono/zod-openapi to v0.18.3 [`#2654`](https://github.com/th-ch/youtube-music/pull/2654)
|
||||
- chore(deps): update eslint monorepo to v9.16.0 [`#2656`](https://github.com/th-ch/youtube-music/pull/2656)
|
||||
- chore(deps): update dependency vite-plugin-inspect to v0.10.2 [`#2657`](https://github.com/th-ch/youtube-music/pull/2657)
|
||||
- fix(youtube-music.css): Fixed #2514 [`#2659`](https://github.com/th-ch/youtube-music/pull/2659)
|
||||
- fix: Fixed Skip Disliked Song not working [`#2651`](https://github.com/th-ch/youtube-music/pull/2651)
|
||||
- fix(deps): update dependency @hono/zod-openapi to v0.18.2 [`#2650`](https://github.com/th-ch/youtube-music/pull/2650)
|
||||
- chore(deps): update dependency vite-plugin-inspect to v0.10.1 [`#2652`](https://github.com/th-ch/youtube-music/pull/2652)
|
||||
- chore(deps): update dependency electron to v33.2.1 [`#2649`](https://github.com/th-ch/youtube-music/pull/2649)
|
||||
- chore(deps): update dependency vite-plugin-inspect to v0.10.0 [`#2646`](https://github.com/th-ch/youtube-music/pull/2646)
|
||||
- chore(deps): update dependency vite to v6 [`#2644`](https://github.com/th-ch/youtube-music/pull/2644)
|
||||
- fix(deps): update dependency @hono/swagger-ui to v0.5.0 [`#2643`](https://github.com/th-ch/youtube-music/pull/2643)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.109 [`#2642`](https://github.com/th-ch/youtube-music/pull/2642)
|
||||
- chore(deps): update dependency vite-plugin-solid to v2.11.0 [`#2641`](https://github.com/th-ch/youtube-music/pull/2641)
|
||||
- fix(deps): update dependency hono to v4.6.12 [`#2636`](https://github.com/th-ch/youtube-music/pull/2636)
|
||||
- fix(deps): update dependency i18next to v24.0.2 [`#2637`](https://github.com/th-ch/youtube-music/pull/2637)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.108 [`#2638`](https://github.com/th-ch/youtube-music/pull/2638)
|
||||
- chore(deps): update dependency typescript-eslint to v8.16.0 [`#2639`](https://github.com/th-ch/youtube-music/pull/2639)
|
||||
- chore(deps): update dependency rollup to v4.27.4 [`#2632`](https://github.com/th-ch/youtube-music/pull/2632)
|
||||
- fix(deps): update dependency i18next to v24 [`#2633`](https://github.com/th-ch/youtube-music/pull/2633)
|
||||
- chore(deps): update dependency typescript to v5.7.2 [`#2629`](https://github.com/th-ch/youtube-music/pull/2629)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.107 [`#2627`](https://github.com/th-ch/youtube-music/pull/2627)
|
||||
- fix(deps): update dependency @hono/zod-openapi to v0.18.0 [`#2626`](https://github.com/th-ch/youtube-music/pull/2626)
|
||||
- fix(deps): update dependency i18next to v23.16.8 [`#2625`](https://github.com/th-ch/youtube-music/pull/2625)
|
||||
- chore(deps): update dependency vite-plugin-inspect to v0.8.8 [`#2623`](https://github.com/th-ch/youtube-music/pull/2623)
|
||||
- fix(deps): update dependency hono to v4.6.11 [`#2624`](https://github.com/th-ch/youtube-music/pull/2624)
|
||||
- chore(deps): update playwright monorepo to v1.49.0 [`#2617`](https://github.com/th-ch/youtube-music/pull/2617)
|
||||
- chore(deps): update dependency rollup to v4.27.3 [`#2610`](https://github.com/th-ch/youtube-music/pull/2610)
|
||||
- chore(deps): update dependency typescript-eslint to v8.15.0 [`#2611`](https://github.com/th-ch/youtube-music/pull/2611)
|
||||
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.11.0 [`#2618`](https://github.com/th-ch/youtube-music/pull/2618)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.105 [`#2603`](https://github.com/th-ch/youtube-music/pull/2603)
|
||||
- chore(deps): update dependency rollup to v4.27.2 [`#2604`](https://github.com/th-ch/youtube-music/pull/2604)
|
||||
- chore(deps): update eslint monorepo to v9.15.0 [`#2607`](https://github.com/th-ch/youtube-music/pull/2607)
|
||||
- fix(deps): update dependency @hono/zod-openapi to v0.17.1 [`#2608`](https://github.com/th-ch/youtube-music/pull/2608)
|
||||
- fix(ambient-mode): fix ambient-mode overlapping other elements [`#2609`](https://github.com/th-ch/youtube-music/pull/2609)
|
||||
- fix: Allow media playback control (MPRIS) for flatpak [`#2606`](https://github.com/th-ch/youtube-music/pull/2606)
|
||||
- fix(deps): update dependency @hono/node-server to v1.13.7 [`#2598`](https://github.com/th-ch/youtube-music/pull/2598)
|
||||
- chore(deps): update dependency rollup to v4.26.0 [`#2600`](https://github.com/th-ch/youtube-music/pull/2600)
|
||||
- fix(deps): update dependency hono to v4.6.10 [`#2601`](https://github.com/th-ch/youtube-music/pull/2601)
|
||||
- fix(deps): update dependency @hono/node-server to v1.13.6 [`#2594`](https://github.com/th-ch/youtube-music/pull/2594)
|
||||
- chore(deps): update dependency vite to v5.4.11 [`#2595`](https://github.com/th-ch/youtube-music/pull/2595)
|
||||
- chore(deps): update dependency typescript-eslint to v8.14.0 [`#2596`](https://github.com/th-ch/youtube-music/pull/2596)
|
||||
- chore(deps): update dependency electron to v33.2.0 [`#2591`](https://github.com/th-ch/youtube-music/pull/2591)
|
||||
- fix(deps): update dependency @hono/zod-openapi to v0.17.0 [`#2592`](https://github.com/th-ch/youtube-music/pull/2592)
|
||||
- fix(deps): update dependency i18next to v23.16.5 [`#2589`](https://github.com/th-ch/youtube-music/pull/2589)
|
||||
- fix(deps): update dependency @hono/node-server to v1.13.5 [`#2578`](https://github.com/th-ch/youtube-music/pull/2578)
|
||||
- fix(deps): update dependency hono to v4.6.9 [`#2579`](https://github.com/th-ch/youtube-music/pull/2579)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.104 [`#2588`](https://github.com/th-ch/youtube-music/pull/2588)
|
||||
- chore(deps): update dependency typescript-eslint to v8.13.0 [`#2581`](https://github.com/th-ch/youtube-music/pull/2581)
|
||||
- chore(deps): update dependency rollup to v4.25.0 [`#2580`](https://github.com/th-ch/youtube-music/pull/2580)
|
||||
- chore(docs): Update screenshot [`#2587`](https://github.com/th-ch/youtube-music/pull/2587)
|
||||
- chore(docs): Specify full path to xattr for macOS, fixes #2583 [`#2586`](https://github.com/th-ch/youtube-music/pull/2586)
|
||||
- fix: callback for time-changed event [`#2577`](https://github.com/th-ch/youtube-music/pull/2577)
|
||||
- chore(deps): update eslint monorepo to v9.14.0 [`#2573`](https://github.com/th-ch/youtube-music/pull/2573)
|
||||
- chore(deps): update dependency utf-8-validate to v6.0.5 [`#2572`](https://github.com/th-ch/youtube-music/pull/2572)
|
||||
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.10.1 [`#2571`](https://github.com/th-ch/youtube-music/pull/2571)
|
||||
- fix(deps): update dependency @hono/node-server to v1.13.4 [`#2570`](https://github.com/th-ch/youtube-music/pull/2570)
|
||||
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.10.0 [`#2569`](https://github.com/th-ch/youtube-music/pull/2569)
|
||||
- fix(deps): update dependency @floating-ui/dom to v1.6.12 [`#2568`](https://github.com/th-ch/youtube-music/pull/2568)
|
||||
- chore(deps): update dependency rollup to v4.24.3 [`#2565`](https://github.com/th-ch/youtube-music/pull/2565)
|
||||
- fix(deps): update dependency hono to v4.6.8 [`#2564`](https://github.com/th-ch/youtube-music/pull/2564)
|
||||
- chore(deps): update dependency typescript-eslint to v8.12.2 [`#2563`](https://github.com/th-ch/youtube-music/pull/2563)
|
||||
- chore(deps): update dependency typescript-eslint to v8.12.0 [`#2561`](https://github.com/th-ch/youtube-music/pull/2561)
|
||||
- fix(deps): update dependency youtubei.js to v11 [`#2562`](https://github.com/th-ch/youtube-music/pull/2562)
|
||||
- chore(deps): update dependency rollup to v4.24.2 [`#2559`](https://github.com/th-ch/youtube-music/pull/2559)
|
||||
- fix(deps): update dependency @hono/node-server to v1.13.3 [`#2560`](https://github.com/th-ch/youtube-music/pull/2560)
|
||||
- fix(deps): update dependency i18next to v23.16.4 [`#2550`](https://github.com/th-ch/youtube-music/pull/2550)
|
||||
- chore(deps): update playwright monorepo to v1.48.2 [`#2551`](https://github.com/th-ch/youtube-music/pull/2551)
|
||||
- fix(deps): update dependency hono to v4.6.7 [`#2552`](https://github.com/th-ch/youtube-music/pull/2552)
|
||||
- chore(deps): update dependency @babel/runtime to v7.26.0 [`#2548`](https://github.com/th-ch/youtube-music/pull/2548)
|
||||
- chore(deps): update dependency @types/color to v4 [`#2547`](https://github.com/th-ch/youtube-music/pull/2547)
|
||||
- fix(deps): update dependency i18next to v23.16.3 [`#2545`](https://github.com/th-ch/youtube-music/pull/2545)
|
||||
- fix(deps): update dependency solid-js to v1.9.3 [`#2541`](https://github.com/th-ch/youtube-music/pull/2541)
|
||||
- chore(deps): update dependency vite to v5.4.10 [`#2542`](https://github.com/th-ch/youtube-music/pull/2542)
|
||||
- chore(deps): update dependency electron to v33.0.2 [`#2537`](https://github.com/th-ch/youtube-music/pull/2537)
|
||||
- chore(deps): update dependency @babel/runtime to v7.25.9 [`#2538`](https://github.com/th-ch/youtube-music/pull/2538)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.103 [`#2532`](https://github.com/th-ch/youtube-music/pull/2532)
|
||||
- chore(deps): update dependency typescript-eslint to v8.11.0 [`#2534`](https://github.com/th-ch/youtube-music/pull/2534)
|
||||
- fix(deps): update dependency hono to v4.6.6 [`#2536`](https://github.com/th-ch/youtube-music/pull/2536)
|
||||
- fix(tuna-obs): Added song url to tuna-obs plugin [`#2524`](https://github.com/th-ch/youtube-music/pull/2524)
|
||||
- fix(deps): update dependency i18next to v23.16.2 [`#2530`](https://github.com/th-ch/youtube-music/pull/2530)
|
||||
- fix(deps): update dependency i18next to v23.16.1 [`#2529`](https://github.com/th-ch/youtube-music/pull/2529)
|
||||
- chore(deps): update eslint monorepo to v9.13.0 [`#2528`](https://github.com/th-ch/youtube-music/pull/2528)
|
||||
- chore(deps): update dependency typescript-eslint to v8.10.0 [`#2527`](https://github.com/th-ch/youtube-music/pull/2527)
|
||||
- chore(deps): update playwright monorepo to v1.48.1 [`#2516`](https://github.com/th-ch/youtube-music/pull/2516)
|
||||
- chore(deps): update dependency electron to v33.0.1 [`#2523`](https://github.com/th-ch/youtube-music/pull/2523)
|
||||
- fix: disable gpu memory buffer video frames [`#2519`](https://github.com/th-ch/youtube-music/pull/2519)
|
||||
- fix: use HEAD instead of GET in songInfo.imageSrc validation step [`#2766`](https://github.com/th-ch/youtube-music/issues/2766)
|
||||
- fix: Fixed #1796 (#2736) [`#1796`](https://github.com/th-ch/youtube-music/issues/1796)
|
||||
- fix(album-actions): Fixed #2312 (#2676) [`#2312`](https://github.com/th-ch/youtube-music/issues/2312) [`#2312`](https://github.com/th-ch/youtube-music/issues/2312)
|
||||
- fix(youtube-music.css): Fixed #2514 (#2659) [`#2514`](https://github.com/th-ch/youtube-music/issues/2514)
|
||||
- chore(docs): Specify full path to xattr for macOS, fixes #2583 (#2586) [`#2583`](https://github.com/th-ch/youtube-music/issues/2583)
|
||||
- fix: fix pnpm-lock.yaml [`3208bf4`](https://github.com/th-ch/youtube-music/commit/3208bf4a6d47d824875b06bd031299694482f02d)
|
||||
- Revert "feat: use swc and lightningcss" [`3b50cbc`](https://github.com/th-ch/youtube-music/commit/3b50cbcb6e3163115d52f05075af5d6f25b80660)
|
||||
- feat: use swc and lightningcss [`ae3a289`](https://github.com/th-ch/youtube-music/commit/ae3a28900576ea388666747bc4794577e1d57e23)
|
||||
|
||||
#### [v3.6.2](https://github.com/th-ch/youtube-music/compare/v3.6.1...v3.6.2)
|
||||
|
||||
> 16 October 2024
|
||||
|
||||
- fix(deps): update dependency serve to v14.2.4 [`#2515`](https://github.com/th-ch/youtube-music/pull/2515)
|
||||
- fix(deps): update dependency hono to v4.6.5 [`#2509`](https://github.com/th-ch/youtube-music/pull/2509)
|
||||
- chore(deps): update dependency vite to v5.4.9 [`#2500`](https://github.com/th-ch/youtube-music/pull/2500)
|
||||
- fix(api-server): properly implement next api call [`#2505`](https://github.com/th-ch/youtube-music/pull/2505)
|
||||
- chore(deps): update dependency electron to v33 [`#2507`](https://github.com/th-ch/youtube-music/pull/2507)
|
||||
- chore(deps): update dependency typescript-eslint to v8.9.0 [`#2503`](https://github.com/th-ch/youtube-music/pull/2503)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.102 [`#2501`](https://github.com/th-ch/youtube-music/pull/2501)
|
||||
- fix: trustedTypes issue [`#2339`](https://github.com/th-ch/youtube-music/issues/2339)
|
||||
- chore(i18n): Translated using Weblate (Icelandic) [`5f79b7e`](https://github.com/th-ch/youtube-music/commit/5f79b7e788c47b0a27a4967c9f3a9e20b483cd75)
|
||||
- chore(i18n): Translated using Weblate (Chinese (Traditional Han script)) [`12d6939`](https://github.com/th-ch/youtube-music/commit/12d693921e26a5c54015673a404e005d1a7175a4)
|
||||
- chore(i18n): Translated using Weblate (Ukrainian) [`836cedb`](https://github.com/th-ch/youtube-music/commit/836cedb0f317b74bf2fc3ec2d1aa865719f46ec0)
|
||||
|
||||
#### [v3.6.1](https://github.com/th-ch/youtube-music/compare/v3.6.0...v3.6.1)
|
||||
|
||||
> 14 October 2024
|
||||
|
||||
- fix(api-server): Various fixes and improvements [`#2496`](https://github.com/th-ch/youtube-music/pull/2496)
|
||||
- fix(deps): update dependency electron-debug to v4.1.0 [`#2499`](https://github.com/th-ch/youtube-music/pull/2499)
|
||||
- fix(renderer): fix force like buttons display logic [`#2493`](https://github.com/th-ch/youtube-music/pull/2493)
|
||||
- fix(deps): update dependency i18next to v23.16.0 [`#2492`](https://github.com/th-ch/youtube-music/pull/2492)
|
||||
- fix(downloader): fix #2371 [`#2371`](https://github.com/th-ch/youtube-music/issues/2371)
|
||||
- fix(ytm-bugs): incorrect video ratio [`#2459`](https://github.com/th-ch/youtube-music/issues/2459)
|
||||
- fix(api-server): fix init/authentication error [`#2497`](https://github.com/th-ch/youtube-music/issues/2497)
|
||||
- fix: RSS feed CORS issue [`#1620`](https://github.com/th-ch/youtube-music/issues/1620)
|
||||
- chore(flatpak-builder): Add more details when failing [`d3acb49`](https://github.com/th-ch/youtube-music/commit/d3acb4945a8dcde6598c53d8207bbf16eda8c739)
|
||||
- chore(i18n): Translated using Weblate (Filipino) [`e428708`](https://github.com/th-ch/youtube-music/commit/e4287085a11f30d141148ab0432cc684819fd0d0)
|
||||
- Bump version to 3.6.1 [`b668730`](https://github.com/th-ch/youtube-music/commit/b6687307dfe7ef765517019093c8db3c2ad14417)
|
||||
|
||||
#### [v3.6.0](https://github.com/th-ch/youtube-music/compare/v3.5.3...v3.6.0)
|
||||
|
||||
> 13 October 2024
|
||||
|
||||
- feat(api-server): remote control api [`#1909`](https://github.com/th-ch/youtube-music/pull/1909)
|
||||
- chore(deps): update playwright monorepo to v1.48.0 [`#2489`](https://github.com/th-ch/youtube-music/pull/2489)
|
||||
- fix(`synced-lyrics`): Fix 2 issues [`#2441`](https://github.com/th-ch/youtube-music/pull/2441)
|
||||
|
||||
|
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 721 KiB |
@ -12,7 +12,7 @@
|
||||
|
||||
</div>
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
<div align="center">
|
||||
@ -182,7 +182,7 @@ brew install th-ch/youtube-music/youtube-music
|
||||
Si instalas la aplicación manualmente y obtienes un error "está dañado y no se puede abrir" al iniciar la aplicación, ejecuta lo siguiente en la Terminal:
|
||||
|
||||
```bash
|
||||
xattr -cr /Applications/YouTube\ Music.app
|
||||
/usr/bin/xattr -cr /Applications/YouTube\ Music.app
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
</div>
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
<div align="center">
|
||||
@ -66,7 +66,7 @@
|
||||
## Plugins disponibles :
|
||||
|
||||
- **Bloqueur de publicités** : Bloquez toutes les publicités et le suivi dès le départ
|
||||
|
||||
|
||||
- **Actions d'album** : Ajoute des boutons Je n'aime pas, Dislike, J'aime, et Unlike pour appliquer cela à toutes les chansons dans une playlist ou un album
|
||||
|
||||
- **Thème de couleur d'album** : Applique un thème dynamique et des effets visuels basés sur la palette de couleurs de l'album
|
||||
@ -185,7 +185,7 @@ brew install th-ch/youtube-music/youtube-music
|
||||
Si vous installez l'application manuellement et obtenez une erreur "est endommagé et ne peut pas être ouvert." lors du lancement de l'application, exécutez ce qui suit dans le Terminal :
|
||||
|
||||
```bash
|
||||
xattr -cr /Applications/YouTube\ Music.app
|
||||
/usr/bin/xattr -cr /Applications/YouTube\ Music.app
|
||||
```
|
||||
|
||||
### Windows
|
||||
@ -385,4 +385,4 @@ MIT © [th-ch](https://github.com/th-ch/youtube-music)
|
||||
|
||||
### Pourquoi le menu de l'application ne s'affiche-t-il pas ?
|
||||
|
||||
Si l'option `Masquer le menu` est activée - vous pouvez afficher le menu avec la touche <kbd>alt</kbd> (ou <kbd>\`</kbd> [backtick] si vous utilisez le plugin du menu intégré)
|
||||
Si l'option `Masquer le menu` est activée - vous pouvez afficher le menu avec la touche <kbd>alt</kbd> (ou <kbd>\`</kbd> [backtick] si vous utilisez le plugin du menu intégré)
|
||||
|
||||
374
docs/readme/README-hu.md
Normal file
@ -0,0 +1,374 @@
|
||||
<div align="center">
|
||||
|
||||
# YouTube Music
|
||||
|
||||
[](https://github.com/th-ch/youtube-music/releases/)
|
||||
[](https://github.com/th-ch/youtube-music/blob/master/LICENSE)
|
||||
[](https://github.com/th-ch/youtube-music/blob/master/eslint.config.mjs)
|
||||
[](https://GitHub.com/th-ch/youtube-music/releases/)
|
||||
[](https://GitHub.com/th-ch/youtube-music/releases/)
|
||||
[](https://aur.archlinux.org/packages/youtube-music-bin)
|
||||
[](https://snyk.io/test/github/th-ch/youtube-music)
|
||||
|
||||
</div>
|
||||
|
||||

|
||||
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/th-ch/youtube-music/releases/latest">
|
||||
<img src="../../web/youtube-music-hu.svg" width="400" height="100" alt="YouTube Music SVG">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Olvasd el más nyelveken: [🏴 Angol](./blob/master/README.md), [🇰🇷 Korea](./docs/readme/README-ko.md), [🇮🇸 Izland](./docs/readme/README-is.md), [🇪🇸 Spanyol](./docs/readme/README-es.md), [🇷🇺 Orosz](./docs/readme/README-ru.md)
|
||||
|
||||
**Electron keretrendszerre épülő alkalmazás a YouTube Music számára, amely a következőket kínálja:**
|
||||
|
||||
- Natív megjelenés és élmény, amely az eredeti felület megtartására törekszik
|
||||
- Egyedi bővítmények keretrendszere: alakítsd át a YouTube Music-ot igényeid szerint (stílus, tartalom, funkciók), engedélyezd/tiltsd le a bővítményeket egy kattintással
|
||||
|
||||
## Bemutató kép
|
||||
|
||||
| Lejátszó ablak (album színtéma és környezeti fény) |
|
||||
|:---------------------------------------------------------------------------------------------------------:|
|
||||
||
|
||||
|
||||
## Tartalom
|
||||
|
||||
- [Funkciók](#Funkciók)
|
||||
- [Elérhető bővítmények](#Elérhető-bővítmények)
|
||||
- [Fordítás](#Fordítás)
|
||||
- [Letöltés](#Letöltés)
|
||||
- [Arch Linux](#arch-linux)
|
||||
- [MacOS](#macos)
|
||||
- [Windows](#windows)
|
||||
- [Hogyan telepítsük hálózati kapcsolat nélkül? (Windows alatt)](#Hogyan-telepítsd-hálózati-kapcsolat-nélkül-Windows)
|
||||
- [Témák](#Témák)
|
||||
- [Fejlesztés](#Fejlesztés)
|
||||
- [Saját bővítmények készítése](#Saját-bővítmények-készítése)
|
||||
- [Bővítmény létrehozása](#Bővítmény-létrehozása)
|
||||
- [Gyakori használati esetek](#Gyakori-használati-esetek)
|
||||
- [Build](#build)
|
||||
- [Gyártás előnézete](#Gyártás-előnézete)
|
||||
- [Tesztelés](#Tesztelés)
|
||||
- [Licenc](#Licenc)
|
||||
- [GYIK](#GYIK)
|
||||
|
||||
## Funkciók:
|
||||
|
||||
- **Automatikus megerősítés a lejátszás szüneteltetésekor** (Alapból engedélyezve): Kikapcsolja a ["Folytatja a nézést?"](https://i.imgur.com/z2mG0QN.png)
|
||||
felugró ablakot, amely bizonyos idő után leállítja a zenét.
|
||||
|
||||
- És még sok más ...
|
||||
|
||||
## Elérhető bővítmények:
|
||||
|
||||
- **Reklámblokkoló**: Blokkolja az összes hirdetést és nyomkövetőt.
|
||||
|
||||
- **Album műveletek**: Dislike, Undislike, Like, Unlike gombok hozzáadása, amivel ezt a lejátszási listán vagy albumban lévő összes dalra alkalmazza.
|
||||
|
||||
- **Album színtéma**: Dinamikus téma és vizuális effektek alkalmazása az album színpalettája alapján.
|
||||
|
||||
- **Ambient mód**: Fényhatás alkalmazása a videóból származó lágy színek vetítésével a képernyő hátterére.
|
||||
|
||||
- **Hangtömörítő**: 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)
|
||||
|
||||
- **Navigációs sáv elmosása**: Átlátszóvá és elmosódottá teszi a navigációs sávot.
|
||||
|
||||
- **Korellenőrzés kihagyása**: A YouTube korellenőrzését kihagyja, ezáltal nem kel meg erősíteni a zene meghallgatása elött. (automatikusan megerősítve lesz)
|
||||
|
||||
- **Feliratválasztó**: Felirat választó a YouTube Music zenékhez.
|
||||
|
||||
- **Kompakt oldalsáv**: Mindig becsukva tartja a bal oldali sávot, ahol a Kezdőlap, Felfedezés, Könyvtár és egyebek láthatók. (amit bármikor ki lehet nyitni)
|
||||
|
||||
- **Áttünés**: Áttünést biztosít a dalok között, ami folytonossá teszi a zenehallgatást anélkül, hogy érezhető lenne a váltás.
|
||||
|
||||
- **Automatikus lejátszás letiltása**: 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)
|
||||
|
||||
- **[Discord](https://discord.com/) Rich Presence**: Mutassa meg barátainak, hogy mit hallgat a [Rich Presence](https://i.imgur.com/nCeVQB2.png) segítségével. (Ehez a Discord-on is engedélyezve kel lennie a Tevékenységállapot megosztásának [DC Beállítások -> Tevékenyég-adatvédelem -> Megoszthatod az észlelt tevékenységeidet másokkal])
|
||||
|
||||
- **Letöltő**: MP3/forrás hanganyag letöltése [közvetlenül az interfészről](https://i.imgur.com/ghqBNVe.png). [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
|
||||
|
||||
- **Hangszínszabályzó**: Szűrőket ad hozzá, hogy erősítsd vagy csökkentsd bizonyos frekvenciatartományokat. (pl. basszuskiemelés)
|
||||
|
||||
- **Exponenciális hangerő**: A hangerő csúszka [exponenciálissá](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) tételével könnyebbé válik az alacsony hangerő kiválasztása.
|
||||
|
||||
- **Alkalmazáson belüli menü**: [A Menüsáv stílusos, sötét vagy album-színű megjelenítése](https://i.imgur.com/vWvO7Xt.png).
|
||||
|
||||
> (Lásd ezt a [bejegyzést](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709), ha problémád van a menü elérésével, miután engedélyezted ezt a bővítményt és a "menü elrejtése" opciót.
|
||||
|
||||
- **Scrobbler**: Scrobbling támogatást biztosít [Last.fm](https://www.last.fm/) és [ListenBrainz](https://listenbrainz.org/) számára.
|
||||
|
||||
- **Lumia Stream**: [Lumia Stream](https://lumiastream.com/) támogatás hozzáadása.
|
||||
|
||||
- **Lyrics Genius**: Dalszöveg támogatást nyújt a legtöbb dalhoz.
|
||||
|
||||
- **Zene együtt**: Lehetővé teszi a lejátszási listák, dalok megosztását másokkal. Amikor a házigazda lejátszik egy dalt, mindenki ugyanazt a dalt fogja hallani.
|
||||
|
||||
- **Navigáció**: Következő/Vissza navigációs nyilak közvetlenül az interfészbe integrálva, mint a kedvenc böngésződben.
|
||||
|
||||
- **Nincs Google bejelentkezés**: A Bejelentkezés gomb eltávolítása az interfészről (Jobb fentről eltünik a bejelentkezés gomb.)
|
||||
|
||||
- **Értesítések**: Értesítés megjelenítése, amikor egy dal elindul. ([interaktív értesítések](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) elérhetők Windows-on)
|
||||
|
||||
- **Kép a képben**: Lehetővé teszi az alkalmazás kép a képben módra váltását.
|
||||
|
||||
- **Lejátszás sebessége**: Hallgassd gyorsan, hallgassd lassan! [Hozzáad egy csúszkát, amely szabályozza a dal sebességét](https://i.imgur.com/uaNOWOt.png)
|
||||
|
||||
- **Precíz hangerő**: A hangerő precíz szabályozása egérgörgővel/gyorsbillentyűkkel, egy egyedi HUD és testreszabható hangerő csuszka segítségével.
|
||||
|
||||
- **Gyorsbillentyűk (& MPRIS)**: Lehetővé teszi globális gyorsbillentyűk beállítását a lejátszáshoz (lejátszás/szünet/következő/előző), valamint a [média OSD](https://i.imgur.com/o13SpAE.png) kikapcsolását a médiagombok felülírásával. Bekapcsolja a Ctrl/CMD + F billentyűkombinációt a kereséshez, a Linux MPRIS támogatását a médiagombokhoz, és [egyedi gyorsbillentyűket](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50) a [haladó felhasználók](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902) számára.
|
||||
|
||||
- **Nem kedvelt dal kihagyása**: Kihagyja a nem kedvelt dalokat.
|
||||
|
||||
- **Csend kihagyása**: Automatikusan átugorja a csendes szakaszokat.
|
||||
|
||||
- [**SzponzorBlokk**](https://github.com/ajayyy/SponsorBlock): Automatikusan átugorja a nem zenei részeket, például az intrókat/outrokat vagy a zenei videók azon részeit, ahol a dal nem szól.
|
||||
|
||||
- **Szinkronizált dalszövegek**: Szinkronizált dalszövegeket biztosít dalokhoz, [LRClib](https://lrclib.net)-hez hasonló szolgáltatókat használva.
|
||||
|
||||
- **Médiavezérlés a tálcán**: Lejátszás vezérlése a [Windows tálcáról](https://i.imgur.com/eolQfnA.png).
|
||||
|
||||
- **TouchBar**: Egyedi TouchBar elrendezés macOS-hoz.
|
||||
|
||||
- **Tuna OBS**: Integráció az [OBS](https://obsproject.com/) [Tuna](https://obsproject.com/forum/resources/tuna.843/) pluginjával.
|
||||
|
||||
- **Videóminőség modosító**: Lehetővé teszi a videó minőségének megváltoztatását egy [gombbal](https://i.imgur.com/UgpgtHL.png) a videó fedvényen.
|
||||
|
||||
- **Videó váltó**: Hozzáad egy [gombot](https://i.imgur.com/288QE1k.png) a Videó/Dal mód közötti váltáshoz. (opcionálisan teljesen eltávolíthatja a videó fület is)
|
||||
|
||||
- **Vizualizáció**: Különböző zenei vizualizációk.
|
||||
|
||||
|
||||
## Fordítás
|
||||
|
||||
Segíthetsz a fordításban a [Hosted Weblate](https://hosted.weblate.org/projects/youtube-music/) oldalán.
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/youtube-music/">
|
||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/multi-auto.svg" alt="Fordítás állapota" />
|
||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/287x66-black.png" alt="Fordítás állapota" />
|
||||
<img src="https://hosted.weblate.org/widget/youtube-music/i18n/hu/287x66-white.png" alt="Fordítás állapota" />
|
||||
</a>
|
||||
|
||||
## Letöltés
|
||||
|
||||
A [legfrissebb kiadás](https://github.com/th-ch/youtube-music/releases/latest) megtekintésével gyorsan megtalálhatod a legújabb verziót.
|
||||
|
||||
### Arch Linux
|
||||
|
||||
Telepítsd a [`youtube-music-bin`](https://aur.archlinux.org/packages/youtube-music-bin) csomagot az AUR-ból. Az AUR telepítési útmutatóját megtalálod ezen a [wiki oldalon](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
|
||||
|
||||
### macOS
|
||||
|
||||
Telepítheted az alkalmazást Homebrew segítségével (lásd a [cask definíciót](https://github.com/th-ch/homebrew-youtube-music)):
|
||||
|
||||
```bash
|
||||
brew install th-ch/youtube-music/youtube-music
|
||||
```
|
||||
|
||||
Ha manuálisan telepítetted az alkalmazást, és a következő hibát kapod indításkor: "sérült, és nem nyitható meg./is damaged and can’t be opened.", futtasd az alábbi parancsot a Terminálban:
|
||||
|
||||
```bash
|
||||
/usr/bin/xattr -cr /Applications/YouTube\ Music.app
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
A [Scoop csomagkezelő](https://scoop.sh) segítségével telepítheted a `youtube-music` csomagot az [`extras` tárolóból](https://github.com/ScoopInstaller/Extras).
|
||||
|
||||
```bash
|
||||
scoop bucket add extras
|
||||
scoop install extras/youtube-music
|
||||
```
|
||||
|
||||
Alternatívaként használhatod a [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/) eszközt, a Windows 11 hivatalos CLI csomagkezelőjét, hogy telepítsd a `th-ch.YouTubeMusic` csomagot.
|
||||
|
||||
*Megjegyzés: A Microsoft Defender SmartScreen figyelmeztethet vagy blokkolhatja a telepítést, mivel az alkalmazás "ismeretlen kiadótól" származik. Ez a figyelmeztetés akkor is megjelenhet, ha manuálisan töltöd le és próbálod futtatni a GitHubról letöltött (.exe) fájlt. Ebben az esetben kattints a "További információ" gombra, majd válaszd a "Futtatás mindenképp" opciót a telepítés folytatásához.*
|
||||
|
||||
```bash
|
||||
winget install th-ch.YouTubeMusic
|
||||
```
|
||||
|
||||
#### Hogyan telepítsd hálózati kapcsolat nélkül? (Windows)
|
||||
|
||||
- Töltsd le a `*.nsis.7z` fájlt a [kiadás oldal](https://github.com/th-ch/youtube-music/releases/latest)ról, amely megfelel az eszközöd architektúrájának:
|
||||
- `x64` 64 bites Windows-hoz
|
||||
- `ia32` 32 bites Windows-hoz
|
||||
- `arm64` ARM64 Windows-hoz
|
||||
- Töltsd le a telepítőt a [kiadás oldal](https://github.com/th-ch/youtube-music/releases/latest)ról. (`*-Setup.exe`)
|
||||
- Helyezd mindkét fájlt **ugyanabba a könyvtárba**.
|
||||
- Futtasd a telepítőt.
|
||||
|
||||
## Témák
|
||||
|
||||
CSS fájlokat tölthetsz be az alkalmazás megjelenésének megváltoztatásához. (Beállítások > Kinézeti beállítások > Téma)
|
||||
|
||||
Néhány előre definiált téma elérhető itt: https://github.com/kerichdev/themes-for-ytmdesktop-player.
|
||||
|
||||
## Fejlesztés
|
||||
|
||||
```bash
|
||||
git clone https://github.com/th-ch/youtube-music
|
||||
cd youtube-music
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Saját bővítmények készítése
|
||||
|
||||
A bővítmények segítségével a következőket teheted:
|
||||
|
||||
- Az alkalmazás manipulálása: Az Electron `BrowserWindow` objektuma átadásra kerül a bővítménykezelőnek.
|
||||
- Az interfész módosítása: HTML és CSS manipulációval megváltoztathatod az alkalmazás kinézetét.
|
||||
|
||||
### Bővítmény létrehozása
|
||||
|
||||
Hozz létre egy mappát a `src/plugins/YOUR-PLUGIN-NAME` útvonalon:
|
||||
|
||||
- `index.ts`: a bővítmény fő fájlja
|
||||
```typescript
|
||||
import style from './style.css?inline'; // import style as inline
|
||||
|
||||
import { createPlugin } from '@/utils';
|
||||
|
||||
export default createPlugin({
|
||||
name: 'Plugin Label',
|
||||
restartNeeded: true, // if value is true, ytmusic show restart dialog
|
||||
config: {
|
||||
enabled: false,
|
||||
}, // your custom config
|
||||
stylesheets: [style], // your custom style,
|
||||
menu: async ({ getConfig, setConfig }) => {
|
||||
// All *Config methods are wrapped Promise<T>
|
||||
const config = await getConfig();
|
||||
return [
|
||||
{
|
||||
label: 'menu',
|
||||
submenu: [1, 2, 3].map((value) => ({
|
||||
label: `value ${value}`,
|
||||
type: 'radio',
|
||||
checked: config.value === value,
|
||||
click() {
|
||||
setConfig({ value });
|
||||
},
|
||||
})),
|
||||
},
|
||||
];
|
||||
},
|
||||
backend: {
|
||||
start({ window, ipc }) {
|
||||
window.maximize();
|
||||
|
||||
// you can communicate with renderer plugin
|
||||
ipc.handle('some-event', () => {
|
||||
return 'hello';
|
||||
});
|
||||
},
|
||||
// it fired when config changed
|
||||
onConfigChange(newConfig) { /* ... */ },
|
||||
// it fired when plugin disabled
|
||||
stop(context) { /* ... */ },
|
||||
},
|
||||
renderer: {
|
||||
async start(context) {
|
||||
console.log(await context.ipc.invoke('some-event'));
|
||||
},
|
||||
// Only renderer available hook
|
||||
onPlayerApiReady(api: YoutubePlayer, context: RendererContext) {
|
||||
// set plugin config easily
|
||||
context.setConfig({ myConfig: api.getVolume() });
|
||||
},
|
||||
onConfigChange(newConfig) { /* ... */ },
|
||||
stop(_context) { /* ... */ },
|
||||
},
|
||||
preload: {
|
||||
async start({ getConfig }) {
|
||||
const config = await getConfig();
|
||||
},
|
||||
onConfigChange(newConfig) {},
|
||||
stop(_context) {},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Gyakori használati esetek
|
||||
|
||||
- Egyedi CSS injektálása: hozz létre egy `style.css` fájlt ugyanabban a mappában, majd:
|
||||
|
||||
```typescript
|
||||
// index.ts
|
||||
import style from './style.css?inline'; // import style as inline
|
||||
|
||||
import { createPlugin } from '@/utils';
|
||||
|
||||
export default createPlugin({
|
||||
name: 'Plugin Label',
|
||||
restartNeeded: true, // if value is true, ytmusic will show a restart dialog
|
||||
config: {
|
||||
enabled: false,
|
||||
}, // your custom config
|
||||
stylesheets: [style], // your custom style
|
||||
renderer() {} // define renderer hook
|
||||
});
|
||||
```
|
||||
|
||||
- Ha módosítani szeretnéd a HTML-t:
|
||||
|
||||
```typescript
|
||||
import { createPlugin } from '@/utils';
|
||||
|
||||
export default createPlugin({
|
||||
name: 'Plugin Label',
|
||||
restartNeeded: true, // if value is true, ytmusic will show the restart dialog
|
||||
config: {
|
||||
enabled: false,
|
||||
}, // your custom config
|
||||
renderer() {
|
||||
// Remove the login button
|
||||
document.querySelector(".sign-in-link.ytmusic-nav-bar").remove();
|
||||
} // define renderer hook
|
||||
});
|
||||
```
|
||||
|
||||
- Az elülső és hátsó rész közötti kommunikáció: Az Electron ipcMain moduljának használatával valósítható meg. Lásd az `index.ts` fájlt és a `sponsorblock` bővítmény példáját.
|
||||
|
||||
## Build
|
||||
|
||||
1. Klónozd a repót
|
||||
2. Kövesd ezt az [útmutatót](https://pnpm.io/installation), hogy telepítsd a `pnpm` csomagkezelőt.
|
||||
3. Futtasd a következő parancsot `pnpm install --frozen-lockfile` a kellékek telepítéséhez.
|
||||
4. Építsd meg az alkalmazást az operációs rendszerednek megfelelő paranccsal: `pnpm build:OS`
|
||||
|
||||
- `pnpm dist:win` - Windows
|
||||
- `pnpm dist:linux` - Linux (amd64)
|
||||
- `pnpm dist:linux:deb-arm64` - Linux (arm64 Debiánhoz)
|
||||
- `pnpm dist:linux:rpm-arm64` - Linux (arm64 Fedorához)
|
||||
- `pnpm dist:mac` - macOS (amd64)
|
||||
- `pnpm dist:mac:arm64` - macOS (arm64)
|
||||
|
||||
Az alkalmazás építéséhez a [electron-builder](https://github.com/electron-userland/electron-builder) eszközt használáld, amely támogatja a macOS, Linux és Windows platformokat.
|
||||
|
||||
## Gyártás előnézete
|
||||
|
||||
```bash
|
||||
pnpm start
|
||||
```
|
||||
|
||||
## Tesztelés
|
||||
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
A [Playwright](https://playwright.dev/) tesztelési keretrendszert használd az alkalmazás teszteléséhez.
|
||||
|
||||
## Licenc
|
||||
|
||||
MIT © [th-ch](https://github.com/th-ch/youtube-music)
|
||||
|
||||
## GYIK
|
||||
|
||||
### Miért nem jelenik meg az alkalmazás menüje?
|
||||
|
||||
Ha a `menü elrejtése` opció be van kapcsolva, a menüt az <kbd>alt</kbd> billentyűvel jelenítheted meg (vagy az <kbd>`</kbd> [fordított idézőjel] billentyűvel, ha az alkalmazáson belüli menü bővítményt használod).
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
</div>
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
<div align="center">
|
||||
@ -65,7 +65,7 @@
|
||||
## Tiltæk tengiforrit:
|
||||
|
||||
- **Auglýsingablokkari**: Lokaðu fyrir allar auglýsingar og rakningar úr kassanum
|
||||
|
||||
|
||||
- **Albúmsaðgerðir**: Bætir Ódíslika, Mislíkt, Líkt, og Ólíkt til að nota þetta á öll lög á spilunarlista eða albúm
|
||||
|
||||
- **Albúmslitaþema**: Beitir kraftmikið þema og sjónrænum áhrifum sem byggjast á litavali albúmsins
|
||||
@ -180,7 +180,7 @@ brew install th-ch/youtube-music/youtube-music
|
||||
Ef þú setur upp forritið handvirkt og færð villu "er skemmd og ekki er hægt að opna það," þegar þú ræsir forritið skaltu keyra eftirfarandi í flugstöðinni:
|
||||
|
||||
```bash
|
||||
xattr -cr /Applications/YouTube\ Music.app
|
||||
/usr/bin/xattr -cr /Applications/YouTube\ Music.app
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
</div>
|
||||
|
||||

|
||||

|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/th-ch/youtube-music/releases/latest">
|
||||
@ -147,7 +147,7 @@ brew install --cask https://raw.githubusercontent.com/th-ch/youtube-music/master
|
||||
(앱을 수동으로 설치하고) 앱을 실행할 때 `손상되었기 때문에 열 수 없습니다.`라는 오류가 발생하면 터미널에서 다음을 실행하세요:
|
||||
|
||||
```bash
|
||||
xattr -cr /Applications/YouTube\ Music.app
|
||||
/usr/bin/xattr -cr /Applications/YouTube\ Music.app
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
@ -12,12 +12,12 @@
|
||||
|
||||
</div>
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
<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">
|
||||
<img src="../../web/youtube-music.svg" width="400" height="100" alt="YouTube Music SVG">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -36,24 +36,24 @@
|
||||
|
||||
## Содержание
|
||||
|
||||
- [Возможности](#features)
|
||||
- [Доступные плагины](#available-plugins)
|
||||
- [Перевод](#translation)
|
||||
- [Скачать](#download)
|
||||
- [Возможности](#Возможности)
|
||||
- [Доступные плагины](#Доступные-плагины)
|
||||
- [Перевод](#Перевод)
|
||||
- [Скачать](#Скачать)
|
||||
- [Arch Linux](#arch-linux)
|
||||
- [MacOS](#macos)
|
||||
- [Windows](#windows)
|
||||
- [Как установить без подключения к интернету? (в Windows)](#how-to-install-without-a-network-connection-in-windows)
|
||||
- [Темы](#themes)
|
||||
- [Для разработчиков](#dev)
|
||||
- [Создайте свои собственные плагины](#build-your-own-plugins)
|
||||
- [Создание плагина](#creating-a-plugin)
|
||||
- [Примеры использования](#common-use-cases)
|
||||
- [Сборка](#build)
|
||||
- [Предварительный просмотр](#production-preview)
|
||||
- [Тестирование](#tests)
|
||||
- [Лицензия](#license)
|
||||
- [Часто задаваемые вопросы](#faq)
|
||||
- [Как установить без подключения к интернету? (в Windows)](#Установка-без-подключения-к-Интернету-в-Windows)
|
||||
- [Темы](#Темы)
|
||||
- [Для разработчиков](#Для-разработчиков)
|
||||
- [Создайте свои собственные плагины](#Создайте-свои-собственные-плагины)
|
||||
- [Создание плагина](#Создание-плагина)
|
||||
- [Примеры использования](#Примеры-использования)
|
||||
- [Сборка](#Сборка)
|
||||
- [Предварительный просмотр](#Предварительный-просмотр)
|
||||
- [Тестирование](#Тестирование)
|
||||
- [Лицензия](#Лицензия)
|
||||
- [Часто задаваемые вопросы](#Часто-задаваемые-вопросы)
|
||||
|
||||
## Возможности:
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
## Доступные плагины:
|
||||
|
||||
- **Блокировщик рекламы**: Блокирует всю рекламу и трекеры
|
||||
|
||||
|
||||
- **Действия с альбомом**: Добавляет кнопки "Убрать дизлайк", "Дизлайк", "Лайк", "Убрать лайк" и применяет их действия ко всем трекам в плейлисте или альбоме
|
||||
|
||||
- **Цветовая тема альбома**: Применяет динамическую тему и эффекты, основываясь на цветовой палитре альбома
|
||||
@ -137,7 +137,7 @@
|
||||
|
||||
- **Визуализатор**: Различные визуализаторы музыки
|
||||
|
||||
- **Synced Lyrics**:
|
||||
- **Synced Lyrics**:
|
||||
Предоставляет синхронизированные слова для песен из таких источников, как [LRClib](https://lrclib.net).
|
||||
|
||||
## Перевод
|
||||
@ -168,7 +168,7 @@ brew install th-ch/youtube-music/youtube-music
|
||||
Если вы устанавливаете приложение вручную и получаете ошибку "is damaged and can’t be opened.", запустите в терминале следующую команду:
|
||||
|
||||
```bash
|
||||
xattr -cr /Applications/YouTube\ Music.app
|
||||
/usr/bin/xattr -cr /Applications/YouTube\ Music.app
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
@ -147,6 +147,11 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: resolveAlias,
|
||||
},
|
||||
server: {
|
||||
cors: {
|
||||
origin: 'https://music.youtube.com',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (mode === 'development') {
|
||||
|
||||
143
package.json
@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "youtube-music",
|
||||
"desktopName": "com.github.th_ch.youtube_music",
|
||||
"productName": "YouTube Music",
|
||||
"version": "3.6.1",
|
||||
"version": "3.7.3",
|
||||
"description": "YouTube Music Desktop App - including custom plugins",
|
||||
"main": "./dist/main/index.js",
|
||||
"license": "MIT",
|
||||
@ -21,7 +22,7 @@
|
||||
"license",
|
||||
"!node_modules",
|
||||
"node_modules/custom-electron-prompt/**",
|
||||
"node_modules/@cliqz/adblocker-electron-preload/**",
|
||||
"node_modules/@ghostery/adblocker-electron-preload/**",
|
||||
"node_modules/@ffmpeg.wasm/core-mt/**",
|
||||
"!node_modules/**/*.map",
|
||||
"!node_modules/**/*.ts"
|
||||
@ -71,6 +72,11 @@
|
||||
"linux": {
|
||||
"icon": "assets/generated/icons/png",
|
||||
"category": "AudioVideo",
|
||||
"desktop": {
|
||||
"entry": {
|
||||
"StartupWMClass": "com.github.th_ch.youtube_music"
|
||||
}
|
||||
},
|
||||
"target": [
|
||||
{
|
||||
"target": "AppImage",
|
||||
@ -131,7 +137,22 @@
|
||||
},
|
||||
"flatpak": {
|
||||
"description": "YouTube Music Desktop App bundled with custom plugins (and built-in ad blocker / downloader)",
|
||||
"category": "AudioVideo"
|
||||
"category": "AudioVideo",
|
||||
"runtimeVersion": "24.08",
|
||||
"baseVersion": "24.08",
|
||||
"finishArgs": [
|
||||
"--socket=wayland",
|
||||
"--socket=x11",
|
||||
"--share=ipc",
|
||||
"--device=dri",
|
||||
"--socket=pulseaudio",
|
||||
"--share=network",
|
||||
"--filesystem=xdg-music:rw",
|
||||
"--talk-name=org.freedesktop.Notifications",
|
||||
"--talk-name=org.gnome.SessionManager",
|
||||
"--talk-name=org.kde.StatusNotifierWatcher",
|
||||
"--own-name=org.mpris.MediaPlayer2.YoutubeMusic.*"
|
||||
]
|
||||
},
|
||||
"deb": {
|
||||
"depends": [
|
||||
@ -177,6 +198,7 @@
|
||||
"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",
|
||||
"dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never",
|
||||
@ -200,116 +222,119 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"usocket": "1.0.1",
|
||||
"node-gyp": "10.2.0",
|
||||
"vite": "6.1.0",
|
||||
"node-gyp": "11.1.0",
|
||||
"xml2js": "0.6.2",
|
||||
"node-fetch": "3.3.2",
|
||||
"@electron/universal": "2.0.1",
|
||||
"@babel/runtime": "7.25.7"
|
||||
"@babel/runtime": "7.26.9"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"vudio@2.1.1": "patches/vudio@2.1.1.patch",
|
||||
"app-builder-lib@24.13.3": "patches/app-builder-lib@24.13.3.patch",
|
||||
"app-builder-lib@26.0.6": "patches/app-builder-lib@26.0.6.patch",
|
||||
"@malept/flatpak-bundler": "patches/@malept__flatpak-bundler.patch"
|
||||
}
|
||||
},
|
||||
"neverBuiltDependencies": []
|
||||
},
|
||||
"dependencies": {
|
||||
"@cliqz/adblocker-electron": "1.27.1",
|
||||
"@cliqz/adblocker-electron-preload": "1.27.1",
|
||||
"@electron-toolkit/tsconfig": "1.0.1",
|
||||
"@electron/remote": "2.1.2",
|
||||
"@ffmpeg.wasm/core-mt": "0.12.0",
|
||||
"@ffmpeg.wasm/main": "0.12.0",
|
||||
"@floating-ui/dom": "1.6.11",
|
||||
"@floating-ui/dom": "1.6.13",
|
||||
"@foobar404/wave": "2.0.5",
|
||||
"@hono/node-server": "1.13.2",
|
||||
"@hono/swagger-ui": "0.4.1",
|
||||
"@hono/zod-openapi": "0.16.4",
|
||||
"@hono/zod-validator": "0.4.1",
|
||||
"@ghostery/adblocker-electron": "2.5.0",
|
||||
"@ghostery/adblocker-electron-preload": "2.5.0",
|
||||
"@hono/node-server": "1.13.8",
|
||||
"@hono/swagger-ui": "0.5.0",
|
||||
"@hono/zod-openapi": "0.18.4",
|
||||
"@hono/zod-validator": "0.4.3",
|
||||
"@jellybrick/dbus-next": "0.10.3",
|
||||
"@jellybrick/electron-better-web-request": "1.0.4",
|
||||
"@jellybrick/mpris-service": "2.1.4",
|
||||
"@jimp/plugin-invert": "0.22.12",
|
||||
"@jellybrick/mpris-service": "2.1.5",
|
||||
"@jimp/plugin-color": "1.6.0",
|
||||
"@skyra/jaro-winkler": "1.1.1",
|
||||
"@xhayper/discord-rpc": "1.2.0",
|
||||
"async-mutex": "0.5.0",
|
||||
"bgutils-js": "3.1.3",
|
||||
"butterchurn": "3.0.0-beta.4",
|
||||
"butterchurn-presets": "3.0.0-beta.4",
|
||||
"color": "4.2.3",
|
||||
"conf": "13.0.1",
|
||||
"color": "5.0.0",
|
||||
"conf": "13.1.0",
|
||||
"custom-electron-prompt": "1.5.8",
|
||||
"dbus-next": "0.10.2",
|
||||
"deepmerge-ts": "7.1.3",
|
||||
"deepmerge-ts": "7.1.4",
|
||||
"electron-debug": "4.1.0",
|
||||
"electron-is": "3.0.0",
|
||||
"electron-localshortcut": "3.2.1",
|
||||
"electron-store": "10.0.0",
|
||||
"electron-store": "10.0.1",
|
||||
"electron-unhandled": "4.0.1",
|
||||
"electron-updater": "6.3.9",
|
||||
"fast-average-color": "9.4.0",
|
||||
"fast-equals": "5.0.1",
|
||||
"fast-equals": "5.2.2",
|
||||
"filenamify": "6.0.0",
|
||||
"hono": "4.6.4",
|
||||
"happy-dom": "17.1.0",
|
||||
"hono": "4.7.1",
|
||||
"howler": "2.2.4",
|
||||
"html-to-text": "9.0.5",
|
||||
"i18next": "23.16.0",
|
||||
"i18next": "24.2.2",
|
||||
"jimp": "1.6.0",
|
||||
"keyboardevent-from-electron-accelerator": "2.0.0",
|
||||
"keyboardevents-areequal": "0.2.2",
|
||||
"node-html-parser": "6.1.13",
|
||||
"node-id3": "0.2.6",
|
||||
"node-html-parser": "7.0.1",
|
||||
"node-id3": "0.2.7",
|
||||
"peerjs": "1.5.4",
|
||||
"semver": "7.6.3",
|
||||
"serve": "14.2.3",
|
||||
"semver": "7.7.1",
|
||||
"serve": "14.2.4",
|
||||
"simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9",
|
||||
"solid-floating-ui": "0.3.1",
|
||||
"solid-js": "1.9.2",
|
||||
"solid-js": "1.9.4",
|
||||
"solid-styled-components": "0.28.5",
|
||||
"solid-transition-group": "0.2.3",
|
||||
"ts-morph": "24.0.0",
|
||||
"solid-transition-group": "0.3.0",
|
||||
"ts-morph": "25.0.1",
|
||||
"vudio": "2.1.1",
|
||||
"x11": "2.3.0",
|
||||
"youtubei.js": "10.5.0",
|
||||
"zod": "3.23.8"
|
||||
"youtubei.js": "13.0.0",
|
||||
"zod": "3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.12.0",
|
||||
"@playwright/test": "1.48.0",
|
||||
"@stylistic/eslint-plugin-js": "2.9.0",
|
||||
"@eslint/js": "9.20.0",
|
||||
"@playwright/test": "1.50.1",
|
||||
"@stylistic/eslint-plugin-js": "3.1.0",
|
||||
"@total-typescript/ts-reset": "0.6.1",
|
||||
"@types/color": "3.0.6",
|
||||
"@types/electron-localshortcut": "3.1.3",
|
||||
"@types/eslint__js": "8.42.3",
|
||||
"@types/howler": "2.2.12",
|
||||
"@types/html-to-text": "9.0.4",
|
||||
"@types/semver": "7.5.8",
|
||||
"@types/trusted-types": "2.0.7",
|
||||
"bufferutil": "4.0.8",
|
||||
"bufferutil": "4.0.9",
|
||||
"builtin-modules": "4.0.0",
|
||||
"cross-env": "7.0.3",
|
||||
"del-cli": "6.0.0",
|
||||
"discord-api-types": "0.37.101",
|
||||
"electron": "32.2.0",
|
||||
"electron-builder": "24.13.3",
|
||||
"electron-devtools-installer": "3.2.0",
|
||||
"electron-vite": "2.3.0",
|
||||
"esbuild": "0.24.0",
|
||||
"eslint": "9.12.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"discord-api-types": "0.37.119",
|
||||
"electron": "34.2.0",
|
||||
"electron-builder": "26.0.6",
|
||||
"electron-builder-squirrel-windows": "26.0.6",
|
||||
"electron-devtools-installer": "4.0.0",
|
||||
"electron-vite": "3.0.0",
|
||||
"esbuild": "0.25.0",
|
||||
"eslint": "9.20.1",
|
||||
"eslint-config-prettier": "10.0.1",
|
||||
"eslint-import-resolver-exports": "1.0.0-beta.5",
|
||||
"eslint-import-resolver-typescript": "3.6.3",
|
||||
"eslint-import-resolver-typescript": "3.8.0",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-prettier": "5.2.1",
|
||||
"glob": "11.0.0",
|
||||
"node-gyp": "10.2.0",
|
||||
"playwright": "1.48.0",
|
||||
"rollup": "4.24.0",
|
||||
"typescript": "5.6.3",
|
||||
"typescript-eslint": "8.8.1",
|
||||
"utf-8-validate": "6.0.4",
|
||||
"vite": "5.4.8",
|
||||
"vite-plugin-inspect": "0.8.7",
|
||||
"eslint-plugin-prettier": "5.2.3",
|
||||
"glob": "11.0.1",
|
||||
"node-gyp": "11.1.0",
|
||||
"playwright": "1.50.1",
|
||||
"rollup": "4.34.7",
|
||||
"typescript": "5.7.3",
|
||||
"typescript-eslint": "8.24.0",
|
||||
"utf-8-validate": "6.0.5",
|
||||
"vite": "6.1.0",
|
||||
"vite-plugin-inspect": "10.2.1",
|
||||
"vite-plugin-resolve": "2.5.2",
|
||||
"vite-plugin-solid": "2.10.2",
|
||||
"vite-plugin-solid": "2.11.1",
|
||||
"ws": "8.18.0"
|
||||
},
|
||||
"auto-changelog": {
|
||||
|
||||
5880
pnpm-lock.yaml
generated
@ -1,8 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"extends": ["config:recommended"],
|
||||
"labels": ["dependencies"],
|
||||
"postUpdateOptions": ["pnpmDedupe"]
|
||||
}
|
||||
|
||||
@ -195,6 +195,7 @@
|
||||
},
|
||||
"tray": {
|
||||
"next": "التالي",
|
||||
"play-pause": "تشغيل/إيقاف",
|
||||
"previous": "السابق",
|
||||
"quit": "خروج",
|
||||
"restart": "إعادة تشغيل التطبيق",
|
||||
@ -206,9 +207,120 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "إذا تم عرض إعلان, فإن الصوت سيتم كتمانه وسيتم وضع سرعة التشغيل الى 16x",
|
||||
"name": "تسريع الإعلان"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "حجب جميع الإعلانات والمسارات خارج الصندوق",
|
||||
"description": "حجب جميع الإعلانات والمتتبعات جاهز للأستخدام",
|
||||
"menu": {
|
||||
"blocker": "حاجب الإعلانات"
|
||||
},
|
||||
"name": "حاجب الإعلانات"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "يضيف أزرار \"إلغاء عدم الإعجاب\"، \"عدم الإعجاب\"، \"الإعجاب\"، و\"إلغاء الإعجاب\" لتطبيقها على جميع الأغاني في قائمة التشغيل أو الألبوم",
|
||||
"name": "إجراءات الألبوم"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "يطبق ثيمًا ديناميكيًا وتأثيرات بصرية بناء على ألوان الألبوم",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "نسبة قوة اللون",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "ثيم ألوان الألبوم"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "يطبق تأثير إضاءة عن طريق إسقاط ألوان ناعمة من الفيديو على خلفية شاشتك",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "مقدار التمويه",
|
||||
"submenu": {
|
||||
"pixels": "{{blurAmount}} بكسل"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"label": "تخزين الصوت الؤقت",
|
||||
"submenu": {
|
||||
"buffer": "{{buffer}}"
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"label": "الشفافية",
|
||||
"submenu": {
|
||||
"percent": "{{opacity}}%"
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "الجودة",
|
||||
"submenu": {
|
||||
"pixels": "{{quality}} بكسل"
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"label": "الحجم",
|
||||
"submenu": {
|
||||
"percent": "{{size}}%"
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"label": "انتقال السلاسة",
|
||||
"submenu": {
|
||||
"during": "خلال {{interpolationTime}} ثانيه"
|
||||
}
|
||||
},
|
||||
"use-fullscreen": {
|
||||
"label": "استخدام شاشه كامله"
|
||||
}
|
||||
},
|
||||
"name": "الوضع المحيطي"
|
||||
},
|
||||
"api-server": {
|
||||
"description": "يضيف خادم للتحكم في المشغل",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "سماح",
|
||||
"deny": "رفض"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hostname": {
|
||||
"label": "اسم المضيف"
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"title": "اسم الخادم"
|
||||
}
|
||||
}
|
||||
},
|
||||
"blur-nav-bar": {
|
||||
"description": "يجعل شريط التنقل شفاف و ضبابي"
|
||||
},
|
||||
"bypass-age-restrictions": {
|
||||
"description": "تجاوز تَحَقّق اليوتيوب من السن",
|
||||
"name": "تجاوز التحقق من السن"
|
||||
},
|
||||
"downloader": {
|
||||
"backend": {
|
||||
"feedback": {
|
||||
"downloading-counter": "تنزيل {{current}}/{{total}}…",
|
||||
"error-while-downloading": "خطأ في تحميل \"{{author}} - {{title}}\": {{error}}",
|
||||
"loading": "جار التحميل…",
|
||||
"preparing-file": "يتم تجهيز الملف…",
|
||||
"saving": "يتم الحفظ…",
|
||||
"video-id-not-found": "لم يتم ايجاد الفيديو"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"choose-download-folder": "اختر مكان التحميل"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,7 +220,7 @@
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Přidává Undislike, Dislike, Like, a Unlike tlačítka k aplikování tohoto ke všem písničkám v seznamu písniček nebo albumu",
|
||||
"name": "Album akce"
|
||||
"name": "Možnosti Albumu"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "Používá dynamický motiv a vizuální efekty na základě palety barev alba",
|
||||
@ -279,6 +279,49 @@
|
||||
},
|
||||
"name": "Ambientní režim"
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Vlož API server abys mohl ovládat přehrávač",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Povolit",
|
||||
"deny": "Zakázat"
|
||||
},
|
||||
"message": "Povolit {{ID}} ({{origin}}) přístup k API?",
|
||||
"title": "dotaz na přihlášení k API"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Možnosti přihlášení",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Ověřit při prvním dotazu"
|
||||
},
|
||||
"none": {
|
||||
"label": "Žádná autorizace"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port"
|
||||
}
|
||||
},
|
||||
"name": "API server [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Zadej hostname API serveru (ve tvaru 0.0.0.0):",
|
||||
"title": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"label": "Zadej port API serveru:",
|
||||
"title": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Apply compression k audiu (snižuje hlasitost nejhlasitěších částí signálu and zvyšuje hlasitost nejjemnějších částí)",
|
||||
"name": "Audio kompresor"
|
||||
@ -415,8 +458,18 @@
|
||||
"menu": {
|
||||
"choose-download-folder": "Vybrat složku pro stahování",
|
||||
"download-finish-settings": {
|
||||
"label": "Stáhnout po dokončení",
|
||||
"prompt": {
|
||||
"last-percent": "Po x procentech",
|
||||
"last-seconds": "Posledních x vteřin",
|
||||
"title": "Nastavit kdy stahovat"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "Pokoročile"
|
||||
"advanced": "Pokoročile",
|
||||
"enabled": "Zapnuto",
|
||||
"mode": "Časový režim",
|
||||
"percent": "Procent",
|
||||
"seconds": "Sekundy"
|
||||
}
|
||||
},
|
||||
"download-playlist": "Stáhnout seznam písniček",
|
||||
@ -616,11 +669,13 @@
|
||||
"name": "Scrobbler",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "Last,fm API klíč"
|
||||
"api-key": "Last,fm API klíč",
|
||||
"api-secret": "Tajný klíč API Last.fm"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "Vložte svůj Listenbrainz user token:"
|
||||
"label": "Vložte svůj Listenbrainz user token:",
|
||||
"title": "ListenBrainz token"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -656,6 +711,22 @@
|
||||
"description": "Automaticky přeskakuje nehudební části jako intro/outro nebo části hudebních videí, kde nehraje písnčka",
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Poskytuje synchronizaci textů do písní, pomocí poskytovatelů, jako je LRClib.",
|
||||
"errors": {
|
||||
"fetch": "Při hledání textu došlo k chybě. Prosím skuste to znovu později.",
|
||||
"not-found": "Žáden text nebyl pro túto skladbu nalezen."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Výchozí znak mezi texty",
|
||||
"tooltip": "Vyberte výchozí znak pro mezeru mezi texty"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Efekt řádku"
|
||||
}
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Ovládejte přehrávání z vašeho Windows hlavního panelu",
|
||||
"name": "Hlavní panel Media Control"
|
||||
|
||||
@ -279,6 +279,56 @@
|
||||
},
|
||||
"name": "Ambiente-Modus"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "Fügt Unterstützung für das Amuse \"Spielt gerade\"-Widget von 6K Labs hinzu",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Amuse API-Server läuft. /query für Liedinformationen."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Fügt einen API-Server hinzu, um die Wiedergabe zu steuern",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Erlauben",
|
||||
"deny": "Ablehnen"
|
||||
},
|
||||
"message": "{{ID}} ({{origin}}) den Zugriff zur API erlauben?",
|
||||
"title": "API-Autorisierungs-Anfrage"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Autorisations-Methode",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Beim ersten Zugriff autorisieren"
|
||||
},
|
||||
"none": {
|
||||
"label": "Keine Autorisierung"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port"
|
||||
}
|
||||
},
|
||||
"name": "API-Server [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Hostname des API-Servers vergeben (z. B. 0.0.0.0):",
|
||||
"title": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port des API-Server:",
|
||||
"title": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Kompressor auf Audio anwenden (senkt die Lautstärke der lautesten Teile des Signals und hebt die Lautstärke der leisesten Teile an)",
|
||||
"name": "Audio-Komprimierer"
|
||||
@ -441,6 +491,18 @@
|
||||
"button": "Herunterladen"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Fügt einen Equalizer zum Player hinzu",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Vorgaben",
|
||||
"list": {
|
||||
"bass-booster": "Bass-Verstärker"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Equalizer"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Macht den Lautstärkeregler exponentiell, damit es einfacher ist leise Lautstärken zu wählen.",
|
||||
"name": "Exponentielle Lautstärke"
|
||||
@ -671,8 +733,8 @@
|
||||
"synced-lyrics": {
|
||||
"description": "Bietet synchronisierte Liedtexte zu Songs, verwendet Anbieter wie LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Beim Abrufen des Liedtexts ist ein Fehler aufgetreten. Bitte versuchen Sie es später nochmal.",
|
||||
"not-found": "⚠️ - Kein Text für diesen Song gefunden."
|
||||
"fetch": "⚠️ - \tBeim Abrufen des Liedtexts ist ein Fehler aufgetreten. \n\tBitte versuchen Sie es später nochmal.",
|
||||
"not-found": "⚠️ Kein Text für diesen Song gefunden."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
@ -682,12 +744,17 @@
|
||||
"line-effect": {
|
||||
"label": "Zeileneffekt",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "schick",
|
||||
"tooltip": "Verwende große, app-ähnliche Effekte in der aktuellen Zeile"
|
||||
},
|
||||
"focus": {
|
||||
"label": "Fokussieren",
|
||||
"tooltip": "Nur aktive Zeile weiß darstellen"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Versatz"
|
||||
"label": "Versatz",
|
||||
"tooltip": "Verschiebe die aktuelle Zeile nach rechts"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Skalieren",
|
||||
@ -700,6 +767,10 @@
|
||||
"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)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Zeige die Liedtexte, auch wenn sie ungenau sind.",
|
||||
"tooltip": "Die Erweiterung sucht mit anderen Suchparameter nochmals, wenn der Song nicht gefunden wurde.\nEs kann sein, dass das Ergebnis von der zweiten Anfrage nicht genau ist."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Zeitkodierungen anzeigen",
|
||||
"tooltip": "Zeitkodierungen neben Songtext anzeigen"
|
||||
|
||||
@ -279,6 +279,13 @@
|
||||
},
|
||||
"name": "Ambient Mode"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "Adds YouTube Music support for the Amuse now playing widget by 6K Labs",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Amuse API server is running. GET /query to get song info."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Adds an API server to control the player",
|
||||
"dialog": {
|
||||
@ -714,8 +721,8 @@
|
||||
"synced-lyrics": {
|
||||
"description": "Provides synced lyrics to songs, using providers like LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - An error occurred while fetching the lyrics. Please try again later.",
|
||||
"not-found": "⚠️ - No lyrics found for this song."
|
||||
"fetch": "⚠️\tAn error occurred while fetching the lyrics.\n\tPlease try again later.",
|
||||
"not-found": "⚠️ No lyrics found for this song."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
@ -725,6 +732,10 @@
|
||||
"line-effect": {
|
||||
"label": "Line effect",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "Fancy",
|
||||
"tooltip": "Use large, app-like effects on the current line"
|
||||
},
|
||||
"focus": {
|
||||
"label": "Focus",
|
||||
"tooltip": "Make only the current line white"
|
||||
@ -808,6 +819,18 @@
|
||||
"visualizer-type": "Visualizer Type"
|
||||
},
|
||||
"name": "Visualizer"
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Adds an equalizer to the player",
|
||||
"name": "Equalizer",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Presets",
|
||||
"list": {
|
||||
"bass-booster": "Bass booster"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,6 +279,13 @@
|
||||
},
|
||||
"name": "Modo ambiente"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "Agrega soporte de YouTube Music para el widget Amuse de reproduciendo ahora de 6K Labs",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "El servidor API de Amuse se está ejecutando. Usa GET /query para obtener información de la canción."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Añade un servidor API para controlar el reproductor",
|
||||
"dialog": {
|
||||
@ -484,6 +491,18 @@
|
||||
"button": "Descargar"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Añade un ecualizador al reproductor",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Ajustes preestablecidos",
|
||||
"list": {
|
||||
"bass-booster": "Amplificador de graves"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Ecualizador"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Hace que el control deslizante de volumen sea exponencial para que sea más fácil seleccionar volúmenes más bajos.",
|
||||
"name": "Volumen exponencial"
|
||||
@ -725,6 +744,10 @@
|
||||
"line-effect": {
|
||||
"label": "Efecto de la línea",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "Elegante",
|
||||
"tooltip": "Usar efectos grandes, similares a los de una aplicación, en la línea actual"
|
||||
},
|
||||
"focus": {
|
||||
"label": "Enfoque",
|
||||
"tooltip": "Mostrar solo la línea actual en blanco"
|
||||
|
||||
@ -67,6 +67,19 @@
|
||||
"restart": "Käivita rakendus uuesti"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"label": "Seadistused",
|
||||
"submenu": {
|
||||
"advanced-options": {
|
||||
"label": "Lisaseadistused",
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "Rakenduse käivitamisel lähtesta puhverdatud andmed",
|
||||
"disable-hardware-acceleration": "Lülita raudvaraline kiirendamine välja",
|
||||
"edit-config-json": "Muuda config.json faili"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"label": "Lisamoodulid",
|
||||
"new": "UUS"
|
||||
|
||||
@ -81,12 +81,12 @@
|
||||
"menu": {
|
||||
"about": "درباره",
|
||||
"navigation": {
|
||||
"label": "ناوبری",
|
||||
"label": "کنترلهای رابط",
|
||||
"submenu": {
|
||||
"copy-current-url": "کپی کردن URL فعلی",
|
||||
"go-back": "بازگشت",
|
||||
"go-forward": "حرکت به جلو",
|
||||
"quit": "خروجی",
|
||||
"copy-current-url": "کپی کردن لینک صفحه فعلی",
|
||||
"go-back": "صفحه قبل",
|
||||
"go-forward": "صفحه بعدی",
|
||||
"quit": "خروج از برنامه",
|
||||
"restart": "راهاندازی مجدد برنامه"
|
||||
}
|
||||
},
|
||||
@ -98,8 +98,8 @@
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "ریست کردن حافظه کش برنامه هنگام شروع",
|
||||
"disable-hardware-acceleration": "غیرفعال کردن شتاب سختافزاری",
|
||||
"edit-config-json": "ویرایش config.json",
|
||||
"override-user-agent": "تغییر User-Agent",
|
||||
"edit-config-json": "config.json ویرایش",
|
||||
"override-user-agent": "User-Agent تغییر",
|
||||
"restart-on-config-changes": "راهاندازی مجدد در صورت تغییرات در پیکربندی",
|
||||
"set-proxy": {
|
||||
"label": "تنظیم پراکسی",
|
||||
@ -109,7 +109,7 @@
|
||||
"title": "تنظیم پراکسی"
|
||||
}
|
||||
},
|
||||
"toggle-dev-tools": "باز کردن DevTools"
|
||||
"toggle-dev-tools": "DevTools باز کردن"
|
||||
}
|
||||
},
|
||||
"always-on-top": "همیشه در بالا",
|
||||
@ -168,7 +168,7 @@
|
||||
},
|
||||
"label": "تم",
|
||||
"submenu": {
|
||||
"import-css-file": "وارد کردن فایل CSS سفارشی",
|
||||
"import-css-file": "سفارشی CSS وارد کردن فایل",
|
||||
"no-theme": "بدون تم"
|
||||
}
|
||||
}
|
||||
@ -177,7 +177,7 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "فعال",
|
||||
"enabled": "فعال/غیرفعال کردن",
|
||||
"label": "افزونهها",
|
||||
"new": "جدید"
|
||||
},
|
||||
@ -187,7 +187,7 @@
|
||||
"force-reload": "اجبار به بارگذاری مجدد",
|
||||
"reload": "بارگذاری مجدد",
|
||||
"reset-zoom": "اندازه واقعی",
|
||||
"toggle-fullscreen": "تغییر به تمامصفحه",
|
||||
"toggle-fullscreen": "تغییر به تمام صفحه",
|
||||
"zoom-in": "بزرگنمایی",
|
||||
"zoom-out": "کوچکنمایی"
|
||||
}
|
||||
@ -197,7 +197,7 @@
|
||||
"next": "بعدی",
|
||||
"play-pause": "پخش/توقف",
|
||||
"previous": "قبلی",
|
||||
"quit": "خروجی",
|
||||
"quit": "خروج",
|
||||
"restart": "راهاندازی مجدد برنامه",
|
||||
"show": "نمایش پنجره",
|
||||
"tooltip": {
|
||||
@ -219,14 +219,17 @@
|
||||
"name": "مسدودکننده تبلیغات"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "افزودن دکمههای \"برگرفتن ناپسند\"، \"ناپسند\"، \"پسند\"، و \"حذف پسند\" برای اعمال آنها روی همه آهنگها در یک فهرست پخش یا آلبوم",
|
||||
"description": "اضافه کردن دکمههای عدم پسندیدن، پسندیدن و لغو پسندیدن برای اعمال این تغییرات به تمامی آهنگهای یک فهرست پخش یا آلبوم",
|
||||
"name": "عملیات آلبوم"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "اعمال یک تم پویا و جلوههای بصری بر اساس پالت رنگ آلبوم",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "نسبت ترکیب رنگ"
|
||||
"label": "نسبت ترکیب رنگ",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "تم رنگ آلبوم"
|
||||
@ -241,10 +244,16 @@
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"label": "بافر"
|
||||
"label": "بافر",
|
||||
"submenu": {
|
||||
"buffer": "{{buffer}}"
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"label": "شفافیت"
|
||||
"label": "شفافیت",
|
||||
"submenu": {
|
||||
"percent": "{{opacity}}%"
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "کیفیت",
|
||||
@ -253,7 +262,10 @@
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"label": "اندازه"
|
||||
"label": "اندازه",
|
||||
"submenu": {
|
||||
"percent": "{{size}}%"
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"label": "انتقال نرمی",
|
||||
@ -267,13 +279,63 @@
|
||||
},
|
||||
"name": "حالت محیطی"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "حالا ویجت Amuse از YouTube Music هم پشتیبانی میکنه! (توسط 6K Labs)",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "سرور Amuse فعال است. برای دریافت اطلاعات آهنگ، از آدرس /query استفاده کنید."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "برای کنترل پخشکننده API افزودن یک سرور",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "اجازه",
|
||||
"deny": "رد کردن"
|
||||
},
|
||||
"message": "اجازه دادن به {{ID}} ({{origin}}) برای دسترسی به API؟",
|
||||
"title": "درخواست مجوز API"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "استراتژی مجوز",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "مجوز در اولین درخواست"
|
||||
},
|
||||
"none": {
|
||||
"label": "بدون نیاز به مجوز"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "نام میزبان"
|
||||
},
|
||||
"port": {
|
||||
"label": "پورت"
|
||||
}
|
||||
},
|
||||
"name": "[بتا]API سرور",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "وارد کنید (مثل 0.0.0.0): API نام میزبان را برای سرور",
|
||||
"title": "نام میزبان"
|
||||
},
|
||||
"port": {
|
||||
"label": "وارد کنید: API پورت را برای سرور",
|
||||
"title": "پورت"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "اعمال فشردهسازی به صدا (کاهش حجم بلندترین بخشهای سیگنال و افزایش حجم بخشهای نرمتر)",
|
||||
"name": "فشردهساز صدا"
|
||||
},
|
||||
"blur-nav-bar": {
|
||||
"description": "شفاف و محو کردن نوار ناوبری",
|
||||
"name": "محو کردن نوار ناوبری"
|
||||
"description": "شفاف و محو کردن نوار کنترل",
|
||||
"name": "محو کردن نوار کنترل"
|
||||
},
|
||||
"bypass-age-restrictions": {
|
||||
"description": "دور زدن تأیید سن یوتیوب",
|
||||
@ -332,27 +394,27 @@
|
||||
},
|
||||
"discord": {
|
||||
"backend": {
|
||||
"already-connected": "تلاش برای اتصال با اتصال فعال",
|
||||
"connected": "متصل به Discord",
|
||||
"disconnected": "قطع اتصال از Discord"
|
||||
"already-connected": "تلاش برای برقراری ارتباط با اتصال فعال",
|
||||
"connected": "متصل به دیسکورد",
|
||||
"disconnected": "ارتباط با دیسکورد قطع شد"
|
||||
},
|
||||
"description": "نمایش آنچه گوش میدهید به دوستان با Rich Presence",
|
||||
"description": "Rich Presence نمایش آنچه گوش میدهید به دوستان با",
|
||||
"menu": {
|
||||
"auto-reconnect": "اتصال خودکار مجدد",
|
||||
"auto-reconnect": "اتصال خودکار",
|
||||
"clear-activity": "پاک کردن فعالیت",
|
||||
"clear-activity-after-timeout": "پاک کردن فعالیت پس از تایماوت",
|
||||
"connected": "متصل",
|
||||
"disconnected": "قطع شده",
|
||||
"clear-activity-after-timeout": "حذف فعالیت پس از اتمام زمان تعیینشده",
|
||||
"connected": "اتصال برقرار شد",
|
||||
"disconnected": "اتصال قطع شد",
|
||||
"hide-duration-left": "مخفی کردن مدت زمان باقیمانده",
|
||||
"hide-github-button": "مخفی کردن دکمه لینک GitHub",
|
||||
"play-on-youtube-music": "پخش در یوتیوب موسیقی",
|
||||
"set-inactivity-timeout": "تنظیم تایماوت عدم فعالیت"
|
||||
"hide-github-button": "مخفی کردن دکمه لینک گیت هاب",
|
||||
"play-on-youtube-music": "پخش در یوتیوب موزیک",
|
||||
"set-inactivity-timeout": "تنظیم زمان عدم فعالیت"
|
||||
},
|
||||
"name": "Rich Presence در Discord",
|
||||
"name": "Discord Rich Presence",
|
||||
"prompt": {
|
||||
"set-inactivity-timeout": {
|
||||
"label": "ورود تایماوت عدم فعالیت به ثانیه:",
|
||||
"title": "تنظیم تایماوت عدم فعالیت"
|
||||
"label": "محدودیت زمان عدم فعالیت را به ثانیه وارد کنید:",
|
||||
"title": "تنظیم زمان عدم فعالیت"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -389,9 +451,386 @@
|
||||
"getting-playlist-info": "در حال دریافت اطلاعات فهرست پخش…",
|
||||
"loading": "در حال بارگذاری…",
|
||||
"playlist-has-only-one-song": "فهرست پخش فقط یک آیتم دارد، به طور مستقیم دانلود میشود",
|
||||
"playlist-id-not-found": "شناسه فهرست پخش یافت نشد"
|
||||
"playlist-id-not-found": "شناسه فهرست پخش یافت نشد",
|
||||
"playlist-is-empty": "فهرست پخش خالی است",
|
||||
"playlist-is-mix-or-private": "خطا در دریافت اطلاعات فهرست پخش: اطمینان حاصل کنید که فهرست پخش خصوصی یا \"مختص شما\" نباشد\n\n{{error}}",
|
||||
"preparing-file": "در حال آمادهسازی فایل…",
|
||||
"saving": "در حال ذخیرهسازی…",
|
||||
"trying-to-get-playlist-id": "تلاش برای دریافت شناسه فهرست پخش: {{playlistId}}",
|
||||
"video-id-not-found": "ویدئو یافت نشد",
|
||||
"writing-id3": "در حال نوشتن تگهای ID3…"
|
||||
}
|
||||
},
|
||||
"description": "دانلود MP3 / صدای منبع به طور مستقیم از رابط",
|
||||
"menu": {
|
||||
"choose-download-folder": "انتخاب پوشه دانلود",
|
||||
"download-finish-settings": {
|
||||
"label": "دانلود پس از پایان",
|
||||
"prompt": {
|
||||
"last-percent": "پس از x درصد",
|
||||
"last-seconds": "آخرین x ثانیه",
|
||||
"title": "پیکربندی زمان دانلود"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "پیشرفته",
|
||||
"enabled": "فعال",
|
||||
"mode": "حالت زمان",
|
||||
"percent": "درصد",
|
||||
"seconds": "ثانیه"
|
||||
}
|
||||
},
|
||||
"download-playlist": "دانلود فهرست پخش",
|
||||
"presets": "پیشتنظیمها",
|
||||
"skip-existing": "رد کردن فایلهای موجود"
|
||||
},
|
||||
"name": "دانلودر",
|
||||
"renderer": {
|
||||
"can-not-update-progress": "امکان بهروزرسانی پیشرفت نیست"
|
||||
},
|
||||
"templates": {
|
||||
"button": "دانلود"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "اضافه کردن یک اکولایزر به پخشکننده",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "تنظیمات از پیش تعیین شده",
|
||||
"list": {
|
||||
"bass-booster": "تقویتکننده باس صدا"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "اکولایزر"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "نوار لغزنده حجم را به صورت نمایی میسازد تا انتخاب حجمهای پایینتر آسانتر شود.",
|
||||
"name": "حجم نمایی"
|
||||
},
|
||||
"in-app-menu": {
|
||||
"description": "منوها را به صورت جذاب، تاریک یا با رنگ آلبوم نمایش میدهد",
|
||||
"menu": {
|
||||
"hide-dom-window-controls": "کنترلهای پنجره DOM را مخفی کن"
|
||||
},
|
||||
"name": "منوی داخل برنامه"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Lumia Stream افزودن پشتیبانی از",
|
||||
"name": "Lumia Stream [بتا]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "افزودن متن ترانه پشتیبان برای اکثر ترانه ها",
|
||||
"menu": {
|
||||
"romanized-lyrics": "الفبای لاتین برای آهنگهایی با الفبای شرقی (فینگلیش)"
|
||||
},
|
||||
"name": "Genius متن آهنگ",
|
||||
"renderer": {
|
||||
"fetched-lyrics": "بازیابی شد Genius متن ترانه توسط"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"description": "اشتراکگذاری فهرست پخش با دیگران. وقتی میزبان آهنگی را پخش میکند، همه بقیه همان آهنگ را میشنوند",
|
||||
"dialog": {
|
||||
"enter-host": "شناسه میزبان را وارد کنید"
|
||||
},
|
||||
"internal": {
|
||||
"save": "ذخیره",
|
||||
"track-source": "منبع آهنگ",
|
||||
"unknown-user": "کاربر ناشناس"
|
||||
},
|
||||
"menu": {
|
||||
"click-to-copy-id": "کپی کردن شناسه میزبان",
|
||||
"close": "بستن Music Together",
|
||||
"connected-users": "کاربران متصل",
|
||||
"disconnect": "قطع اتصال Music Together",
|
||||
"empty-user": "هیچ کاربر متصلی وجود ندارد",
|
||||
"host": "میزبان Music Together",
|
||||
"join": "پیوستن به Music Together",
|
||||
"permission": {
|
||||
"all": "اجازه دادن به مهمانان برای کنترل فهرست پخش و پخشکننده",
|
||||
"host-only": "فقط میزبان میتواند فهرست پخش و پخشکننده را کنترل کند",
|
||||
"playlist": "اجازه دادن به مهمانان برای کنترل فهرست پخش"
|
||||
},
|
||||
"set-permission": "تغییر مجوز کنترل",
|
||||
"status": {
|
||||
"disconnected": "قطع اتصال",
|
||||
"guest": "متصل به عنوان مهمان",
|
||||
"host": "متصل به عنوان میزبان"
|
||||
}
|
||||
},
|
||||
"name": "Music Together [بتا]",
|
||||
"toast": {
|
||||
"add-song-failed": "افزودن آهنگ با شکست مواجه شد",
|
||||
"closed": "بسته شد Music Together",
|
||||
"disconnected": "Music Together قطع اتصال",
|
||||
"host-failed": "با شکست مواجه شد Music Together میزبانی",
|
||||
"id-copied": "شناسه میزبان به کلیپبورد کپی شد",
|
||||
"id-copy-failed": "کپی شناسه میزبان به کلیپبورد با شکست مواجه شد",
|
||||
"join-failed": "با شکست مواجه شد Music Together پیوستن به",
|
||||
"joined": "پیوست Music Together به",
|
||||
"permission-changed": "مجوز Music Together به \"{{permission}}\" تغییر یافت",
|
||||
"remove-song-failed": "حذف آهنگ با شکست مواجه شد",
|
||||
"user-connected": "{{name}} به Music Together پیوست",
|
||||
"user-disconnected": "{{name}} Music Together را ترک کرد"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"description": "بعدی/قبلی به طور مستقیم در رابط یکپارچه شدهاند، مانند مرورگر مورد علاقه شما",
|
||||
"name": "کنترل های رابط"
|
||||
},
|
||||
"no-google-login": {
|
||||
"description": "حذف دکمهها و لینکهای ورود به گوگل از رابط کاربری",
|
||||
"name": "بدون ورود به گوگل"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "نمایش اعلان هنگامی که آهنگی شروع به پخش میکند (اعلانهای تعاملی در ویندوز در دسترس هستند)",
|
||||
"menu": {
|
||||
"interactive": "اعلانهای تعاملی",
|
||||
"interactive-settings": {
|
||||
"label": "تنظیمات تعاملی",
|
||||
"submenu": {
|
||||
"hide-button-text": "مخفی کردن متن دکمه",
|
||||
"refresh-on-play-pause": "تازهسازی در پخش/توقف",
|
||||
"tray-controls": "باز/بسته شدن با کلیک روی آیکون در نوار وظیفه"
|
||||
}
|
||||
},
|
||||
"priority": "اولویت اعلان",
|
||||
"toast-style": "Toast سبک",
|
||||
"unpause-notification": "نمایش اعلان هنگام از سرگیری پخش"
|
||||
},
|
||||
"name": "اعلانها"
|
||||
},
|
||||
"picture-in-picture": {
|
||||
"description": "اجازه میدهد تا برنامه به حالت تصویر در تصویر تغییر کند",
|
||||
"menu": {
|
||||
"always-on-top": "همیشه در بالا",
|
||||
"hotkey": {
|
||||
"label": "کلید میانبر",
|
||||
"prompt": {
|
||||
"keybind-options": {
|
||||
"hotkey": "کلید میانبر"
|
||||
},
|
||||
"label": "یک کلید میانبر انتخاب کنید برای فعال/غیرفعال کردن حالت تصویر در تصویر",
|
||||
"title": "کلید میانبر برای حالت تصویر در تصویر"
|
||||
}
|
||||
},
|
||||
"save-window-position": "ذخیره موقعیت پنجره",
|
||||
"save-window-size": "ذخیره اندازه پنجره",
|
||||
"use-native-pip": "استفاده از حالت تصویر در تصویر اصلی مرورگر"
|
||||
},
|
||||
"name": "تصویر در تصویر",
|
||||
"templates": {
|
||||
"button": "تصویر در تصویر"
|
||||
}
|
||||
},
|
||||
"playback-speed": {
|
||||
"description": "به سرعت گوش بده، به آرامی گوش بده! یک دکمه کشویی برای تنظیم سرعت آهنگ اضافه شد",
|
||||
"name": "سرعت پخش",
|
||||
"templates": {
|
||||
"button": "سرعت"
|
||||
}
|
||||
},
|
||||
"precise-volume": {
|
||||
"description": "کنترل دقیق صدا با استفاده از چرخ موس/میانبرها، همراه با HUD سفارشی و مراحل تنظیم حجم قابل تنظیم",
|
||||
"menu": {
|
||||
"arrows-shortcuts": "میانبرهای کلیدهای فلشی",
|
||||
"custom-volume-steps": "مراحل تنظیم صدای دلخواه",
|
||||
"global-shortcuts": "کلید های میانبر جهانی"
|
||||
},
|
||||
"name": "صدای دقیق",
|
||||
"prompt": {
|
||||
"global-shortcuts": {
|
||||
"keybind-options": {
|
||||
"decrease": "کاهش صدا",
|
||||
"increase": "افزایش صدا"
|
||||
},
|
||||
"label": "انتخاب کلیدهای میانبر سراسری صدا:",
|
||||
"title": "میانبرهای کلید سراسری صدا"
|
||||
},
|
||||
"volume-steps": {
|
||||
"label": "مراحل انتخاب افزایش/کاهش صدا",
|
||||
"title": "سطح صدا"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quality-changer": {
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"quality-changer": {
|
||||
"detail": "کیفیت کنونی: {{quality}}",
|
||||
"message": "انتخاب کیفیت ویدیو:",
|
||||
"title": "انتخاب کیفیت ویدیو"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "امکان تغییر کیفیت ویدیو با استفاده از دکمه در رابط پخش ویدیو",
|
||||
"name": "تغییر دهنده کیفیت ویدیو"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "اضافه کردن پشتیبانی از اسکرابلینگ (etc. last.fm, Listenbrainz)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "احراز هویت با Last.fm ناموفق بود\nپنجره شناور را تا راهاندازی مجدد بعدی مخفی کن.",
|
||||
"title": "احراز هویت ناموفق بود"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "تنظیمات \"Last.fm \"API"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "توکن کاربری ListenBrainz را وارد کنید"
|
||||
},
|
||||
"scrobble-other-media": "ردیابی رسانههای دیگر"
|
||||
},
|
||||
"name": "ابزار ثبتکنندهی آهنگ",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "کلید Last.fm API",
|
||||
"api-secret": "API مخفی Last.fm"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "توکن کاربری ListenBrainz خود را وارد کنید:",
|
||||
"title": "توکن ListenBrainz"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"description": "امکان تنظیم میانبرهای سراسری برای کنترل (پخش/توقف/بعدی/قبلی) و خاموش کردن OSD رسانه با بازنویسی کلیدهای رسانهای، فعالسازی Ctrl/CMD + F برای جستجو، فعالسازی پشتیبانی MPRIS در لینوکس برای کلیدهای رسانهای، و میانبرهای سفارشی برای کاربران پیشرفته",
|
||||
"menu": {
|
||||
"override-media-keys": "تغییر عملکرد کلیدهای رسانه",
|
||||
"set-keybinds": "تنظیم کنترلهای سراسری آهنگ"
|
||||
},
|
||||
"name": "میانبرها (& MPRIS)",
|
||||
"prompt": {
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "بعدی",
|
||||
"play-pause": "پخش / توقف",
|
||||
"previous": "قبلی"
|
||||
},
|
||||
"label": "انتخاب میانبرهای سراسری برای کنترل آهنگها:",
|
||||
"title": "میانبرهای کلیدی سراسری"
|
||||
}
|
||||
}
|
||||
},
|
||||
"skip-disliked-songs": {
|
||||
"description": "خودکار آهنگ های غیر موردعلاقه رد میشن",
|
||||
"name": "رد آهنگهای غیر مورد علاقه"
|
||||
},
|
||||
"skip-silences": {
|
||||
"description": "رد خودکار بخشهای بی صدا آهنگ ها",
|
||||
"name": "رد بخشهای بیصدا"
|
||||
},
|
||||
"sponsorblock": {
|
||||
"description": "بهطور خودکار بخشهای غیرموسیقی مانند مقدمه/پایان یا قسمتهایی از ویدیوهای موسیقی که آهنگ در آن پخش نمیشود را رد میکند",
|
||||
"name": "مسدودکننده اسپانسر"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "ارائه متن ترانهها به صورت هماهنگ با آهنگها، با استفاده از ارائهدهندگانی مانند LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️هنگام بارگیری متن ترانه خطایی رخ داده است.\n\tلطفاً بعداً دوباره تلاش کنید.",
|
||||
"not-found": "⚠️ متنی برای این ترانه پیدا نشد."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "حرف/کاراکتر پیشفرض بین متنهای ترانه",
|
||||
"tooltip": "حرف/کاراکتر پیشفرض را برای فاصله بین متنهای ترانه انتخاب کنید"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "افکت خط متن",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "شیک",
|
||||
"tooltip": "استفاده از افکتهای بزرگ و شبیه به اپلیکیشنها برای خط فعلی"
|
||||
},
|
||||
"focus": {
|
||||
"label": "تمرکز",
|
||||
"tooltip": "فقط خط فعلی رو سفید کن"
|
||||
},
|
||||
"offset": {
|
||||
"label": "جابجایی",
|
||||
"tooltip": "جابجایی خط فعلی به سمت راست"
|
||||
},
|
||||
"scale": {
|
||||
"label": "مقیاس",
|
||||
"tooltip": "تغییر اندازه خط فعلی"
|
||||
}
|
||||
},
|
||||
"tooltip": "افکت مورد نظر را برای خط فعلی انتخاب کنید"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "هماهنگسازی کامل متن ترانه",
|
||||
"tooltip": "محاسبه دقیق نمایش خط بعدی تا میلیثانیه (ممکن است تاثیر کمی بر عملکرد داشته باشد)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "نمایش متن ترانه ها حتی اگر دقیق نباشد",
|
||||
"tooltip": "اگر آهنگ پیدا نشد، افزونه دوباره با یک جستجوی متفاوت امتحان میکند.\nنتیجهی این تلاش ممکن است دقیق نباشد."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "نمایش زمانبندیها",
|
||||
"tooltip": "نمایش زمانبندیها کنار متن ترانه"
|
||||
}
|
||||
},
|
||||
"name": "متن ترانه هماهنگ شد",
|
||||
"refetch-btn": {
|
||||
"fetching": "در حال بارگذاری...",
|
||||
"normal": "دریافت مجدد متن ترانه"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - ممکن است متن ترانه به دلیل عدم تطابق زمان با مشکل هماهنگی مواجه شود.",
|
||||
"inexact": "⚠️ - ممکن است متن ترانه برای این آهنگ دقیق نباشد",
|
||||
"instrumental": "⚠️ - این آهنگ بی کلام است"
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "کنترل پخش از نوار وظیفه ویندوز(taskbar)",
|
||||
"name": "کنترل رسانه از نوار وظیفه (taskbar)"
|
||||
},
|
||||
"touchbar": {
|
||||
"description": "افزودن ویجت TouchBar برای کاربران macOS",
|
||||
"name": "نوار لمسی"
|
||||
},
|
||||
"tuna-obs": {
|
||||
"description": "ادغام با پلاگین Tuna در OBS",
|
||||
"name": "Tuna OBS"
|
||||
},
|
||||
"video-toggle": {
|
||||
"description": "دکمهای اضافه میکند برای جابجایی بین حالت ویدیو/آهنگ. همچنین به صورت اختیاری میتواند تب ویدیو را حذف کند",
|
||||
"menu": {
|
||||
"align": {
|
||||
"label": "چینش",
|
||||
"submenu": {
|
||||
"left": "چپ",
|
||||
"middle": "میانه",
|
||||
"right": "راست"
|
||||
}
|
||||
},
|
||||
"force-hide": "حذف اجباری تب ویدیو",
|
||||
"mode": {
|
||||
"label": "حالت",
|
||||
"submenu": {
|
||||
"custom": "حالت شخصیسازی شده",
|
||||
"disabled": "غیرفعال",
|
||||
"native": "حالت پیشفرض"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "ویدیو به آهنگ",
|
||||
"templates": {
|
||||
"button": "ترانه"
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "اضافه کردن نمایشدهنده تصویری به پخشکننده",
|
||||
"menu": {
|
||||
"visualizer-type": "نوع نمایشدهنده تصویری"
|
||||
},
|
||||
"name": "نمایشدهنده تصویری"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,6 +279,12 @@
|
||||
},
|
||||
"name": "Ambient Mode"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "Nagdaragdag ng suporta sa YouTube Music para sa Amuse now playing widget ng 6K Labs",
|
||||
"response": {
|
||||
"query": "Tumatakbo ang Amuse API server. Gamitin ang GET /query para makuha ang impo ng kanta."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Nagdadagdag ng API Server upang kontrolin ang player",
|
||||
"dialog": {
|
||||
@ -408,7 +414,7 @@
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"error": {
|
||||
"message": "Argh! Paumanhin, nabigo ang pag-download…",
|
||||
"message": "Kainis! Paumanhin, nabigo ang pag-download…",
|
||||
"title": "Nagkaroon ng error sa pag-download!"
|
||||
},
|
||||
"start-download-playlist": {
|
||||
@ -465,7 +471,15 @@
|
||||
"can-not-update-progress": "Hindi ma-update ang progress"
|
||||
},
|
||||
"templates": {
|
||||
"button": "Mag download"
|
||||
"button": "Mag-download"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Nagdaragdag ng equalizer sa player",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Mga Preset"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exponential-volume": {
|
||||
@ -674,8 +688,8 @@
|
||||
"synced-lyrics": {
|
||||
"description": "Nagbibigay ng naka-sync na lyrics sa mga kanta, gamit ang mga provider tulad ng LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Nagkaroon ng error habang kinukuha ang lyrics. Subukang muli mamaya.",
|
||||
"not-found": "⚠️ - Walang nakitang lyrics para sa kantang ito."
|
||||
"fetch": "⚠️\t Nagkaroon ng error habang kinukuha ang lyrics.\n\t Subukang muli mamaya.",
|
||||
"not-found": "⚠️ Walang nakitang lyrics para sa kantang ito."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
@ -685,6 +699,10 @@
|
||||
"line-effect": {
|
||||
"label": "Effect ng Linya",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "Magarbo",
|
||||
"tooltip": "Gumamit ng malaki, mala-app na effect sa kasalukuyang linya"
|
||||
},
|
||||
"focus": {
|
||||
"tooltip": "Gawing puti lamang ang kasalukuyang linya"
|
||||
},
|
||||
|
||||
@ -279,6 +279,49 @@
|
||||
},
|
||||
"name": "Mode ambiant"
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Ajouter un serveur API pour contrôler le lecteur",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Autoriser",
|
||||
"deny": "Interdire"
|
||||
},
|
||||
"message": "Autoriser {{ID}} ({{origin}}) à accéder à l'API ?",
|
||||
"title": "Requête d'autorisation d'API"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Plan d'autorisation",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Autoriser à la première requête"
|
||||
},
|
||||
"none": {
|
||||
"label": "Pas d'autorisation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Nom de l'hôte"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port"
|
||||
}
|
||||
},
|
||||
"name": "Serveur API [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Entrer le nom de l'hôte (par exemple 0.0.0.0) pour le serveur API:",
|
||||
"title": "Nom d'hôte"
|
||||
},
|
||||
"port": {
|
||||
"label": "Entrez le port du serveur de l'API:",
|
||||
"title": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Appliquer une compression à l'audio (diminue le volume des parties les plus fortes du signal et augmente le volume des parties les plus faibles)",
|
||||
"name": "Compresseur audio"
|
||||
@ -441,6 +484,18 @@
|
||||
"button": "Télécharger"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Ajoute un égaliseur au lecteur",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Préréglages",
|
||||
"list": {
|
||||
"bass-booster": "Amplificateur de basses"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Égaliseur"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Rend le curseur de volume exponentiel afin qu'il soit plus facile de sélectionner des volumes plus faibles.",
|
||||
"name": "Volume exponentiel"
|
||||
@ -671,7 +726,7 @@
|
||||
"synced-lyrics": {
|
||||
"description": "Ajoute des paroles synchronisées aux chansons, grâce à LRClib par exemple.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Une erreur s'est produite en allant chercher les paroles. Merci de réessayer plus tard.",
|
||||
"fetch": "⚠️\tUne erreur s'est produite en allant chercher les paroles.\n\tMerci de réessayer plus tard.",
|
||||
"not-found": "⚠️ - Aucune paroles trouvées pour cette musique."
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
"executed-at-ms": "התוסף {{pluginName}}:{{contextName}} בוצע ב {{ms}}ms",
|
||||
"initialize-failed": "טעינת התוסף \"{{pluginName}}\" נכשלה",
|
||||
"load-all": "טוען את כל התוספים",
|
||||
"load-failed": "לא ניתן לטעון את התוסף {{pluginName}}",
|
||||
"load-failed": "שגיאה בטעינת התוסף \"{{pluginName}}\"",
|
||||
"loaded": "התוסף \"{{pluginName}}\" נטען",
|
||||
"unload-failed": "הסרת התוסף \"{{pluginName}} נכשלה",
|
||||
"unloaded": "תוסף {{pluginName}} הורד"
|
||||
@ -52,6 +52,53 @@
|
||||
"buttons": {
|
||||
"later": "אחר כך",
|
||||
"restart-now": "מתחיל את התוכנה מחדש עכשיו"
|
||||
},
|
||||
"detail": "\"{{pluginName}}\" מצריך אתחול",
|
||||
"message": "\"{{pluginName}}\" דורש אתחול",
|
||||
"title": "נדרשת הפעלה מחדש"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "יציאה",
|
||||
"relaunch": "הפעל מחדש",
|
||||
"wait": "המתן"
|
||||
},
|
||||
"detail": "אנו מצטערים על אי הנוחות! אנא בחר מה לעשות:",
|
||||
"message": "האפליקציה אינה מגיבה",
|
||||
"title": "החלון אינו מגיב"
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
"disable": "בטל עדכונים",
|
||||
"download": "הורדה",
|
||||
"ok": "אוקיי"
|
||||
},
|
||||
"detail": "גרסה חדשה זמינה, ניתן להוריד אותה ב-{{downloadLink}}",
|
||||
"message": "גירסה חדשה זמינה כעת",
|
||||
"title": "קיים עדכון חדש"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"about": "אודות",
|
||||
"navigation": {
|
||||
"label": "ניווט",
|
||||
"submenu": {
|
||||
"copy-current-url": "העתק את כתובת ה-URL",
|
||||
"go-back": "חזור אחורה",
|
||||
"go-forward": "לך קדימה",
|
||||
"quit": "יציאה"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"label": "אפשרויות",
|
||||
"submenu": {
|
||||
"advanced-options": {
|
||||
"label": "אפשרויות מתקדמות",
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "אפס את מטמון האפליקציה כאשר האפליקציה מתחילה",
|
||||
"disable-hardware-acceleration": "השבת האצת החומרה"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
291
src/i18n/resources/hi.json
Normal file
@ -0,0 +1,291 @@
|
||||
{
|
||||
"common": {
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "{{pluginName}}::{{contextName}} प्लगइन निष्पादित करने में विफल",
|
||||
"executed-at-ms": "{{pluginName}}::{{contextName}} प्लगिन {{ms}} में निष्पाशित हुआ",
|
||||
"initialize-failed": "\"{{pluginName}}\" प्लगिन इनिशियलाइज़ होने में असफल रहा",
|
||||
"load-all": "सारे प्लगिन लोड हो चुके हैं",
|
||||
"load-failed": "\"{{pluginName}}\" प्लगिन लोड होने में असफल रहा",
|
||||
"loaded": "प्लगिन \"{{pluginName}}\" लोड हो चुका है",
|
||||
"unload-failed": "\"{{pluginName}}\" अनलोड होने में असफल रहा",
|
||||
"unloaded": "प्लगिन \"{{pluginName}}\" अनलोड हो गया है"
|
||||
}
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"code": "hi",
|
||||
"local-name": "हिंदी",
|
||||
"name": "Hindi"
|
||||
},
|
||||
"main": {
|
||||
"console": {
|
||||
"did-finish-load": {
|
||||
"dev-tools": "लोडिंग समाप्त हुई । डेवटूल्स खोले गए हैं"
|
||||
},
|
||||
"i18n": {
|
||||
"loaded": "i18n लोड हो गया है"
|
||||
},
|
||||
"second-instance": {
|
||||
"receive-command": "प्रोटोकॉल पर आदेश प्राप्त हुआ \"{{command}}\""
|
||||
},
|
||||
"theme": {
|
||||
"css-file-not-found": "सीएसएस फाइल \"{{cssFile}}\" मौजूद नही है, अनदेखा किया जा रहा है"
|
||||
},
|
||||
"unresponsive": {
|
||||
"details": "अनरेस्पॉन्सिव एरर\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "एप कैश साफ़ किया जा रहा है"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "विंडो ने ऑफस्क्रीन रेंडर करने का प्रयास किया, विंडो साइज={{windowSize}}, डिस्प्ले साइज={{displaySize}}, पोजिशन={{position}}"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "मेनू छिपा हुआ है, देखने के लिए 'Alt' का इस्तेमाल करें (या 'Escape' अगर आप इन-एप मेनू का उपयोग कर रहे हैं)",
|
||||
"message": "मेनू छिपाएँ सक्षम है",
|
||||
"title": "मेनू छिपाएँ सक्षम"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "बाद में",
|
||||
"restart-now": "पुनः आरंभ करें"
|
||||
},
|
||||
"detail": "\"{{pluginName}}\" प्रभाव लेने के लिए प्लगिन को पुनः शुरू करें",
|
||||
"message": "\"{{pluginName}}\" पुनः आरंभ करने की आवश्यकता है",
|
||||
"title": "पुनः आरंभ करने की आवश्यकता है"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "बंद करें",
|
||||
"relaunch": "पुनः लॉन्च करें",
|
||||
"wait": "रुकें"
|
||||
},
|
||||
"detail": "असुविधाए के लिए खेद हैं! कृपया चुनें कि क्या करना है:",
|
||||
"message": "एप्लीकेशन अनुत्तरदायी है",
|
||||
"title": "विंडो अनुत्तरदायी है"
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
"disable": "अपडेट्स बंद करें",
|
||||
"download": "डाउनलोड",
|
||||
"ok": "ठीक है"
|
||||
},
|
||||
"detail": "एक नया वर्जन उपलब्ध है, {{downloadLink}} से डाउनलोड किया जा सकता है",
|
||||
"message": "एक नया वर्जन उपलब्ध है",
|
||||
"title": "अपडेट उपलब्ध है"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"about": "के बारे में",
|
||||
"navigation": {
|
||||
"label": "मार्गदर्शन",
|
||||
"submenu": {
|
||||
"copy-current-url": "मौजूदा यूआरएल कापी करें",
|
||||
"go-back": "पीछे जाएं",
|
||||
"go-forward": "आगे जाएं",
|
||||
"quit": "निकास",
|
||||
"restart": "एप को पुनः शुरू करें"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"label": "विकल्प",
|
||||
"submenu": {
|
||||
"advanced-options": {
|
||||
"label": "उन्नत विकल्प",
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "एप शुरू होते समय कैश रीसेट करें",
|
||||
"disable-hardware-acceleration": "हार्डवेयर एक्सीलरेशन बंद करें",
|
||||
"edit-config-json": "config.json को एडिट करें",
|
||||
"override-user-agent": "यूजर-एजेंट को रद्द करें",
|
||||
"restart-on-config-changes": "कनफिग बदलने पे पुनः शुरू करें",
|
||||
"set-proxy": {
|
||||
"label": "प्रॉक्सी तय करें",
|
||||
"prompt": {
|
||||
"label": "प्प्रॉक्सी पता डालें: (बंद करने के लिए खाली छोड़ें)",
|
||||
"placeholder": "उदाहरण: SOCKS5://127.0.0.1:9999",
|
||||
"title": "प्रॉक्सी तय करें"
|
||||
}
|
||||
},
|
||||
"toggle-dev-tools": "डेवटूल्स को टॉगल करें"
|
||||
}
|
||||
},
|
||||
"always-on-top": "हमेशा ऊपर",
|
||||
"auto-update": "ऑटो अपडेट",
|
||||
"hide-menu": {
|
||||
"dialog": {
|
||||
"message": "अगले लॉन्च पे मेनू छुपा दिया जायेगा, देखने के लिए [Alt] का प्रयोग करें (या बैकटिक [`] अगर आप इन एप मेनू का प्रयोग कर रहे हैं)",
|
||||
"title": "मेनू छुपाना सक्रिय है"
|
||||
},
|
||||
"label": "मेनू छुपाएं"
|
||||
},
|
||||
"language": {
|
||||
"dialog": {
|
||||
"message": "पुनः शुरू करने के बाद भाषा बदल दी जाएगी",
|
||||
"title": "भाषा बदल दी गई है"
|
||||
},
|
||||
"label": "भाषा",
|
||||
"submenu": {
|
||||
"to-help-translate": "अनुवाद करने में सहायता करना चाहते हैं? यहां दबाएं"
|
||||
}
|
||||
},
|
||||
"resume-on-start": "एप शुरू होने पर आखरी गाना फिर शुरू करें",
|
||||
"single-instance-lock": "सिंगल इंस्टेंस लॉक",
|
||||
"start-at-login": "शुरू होने पे लॉगिन करें",
|
||||
"starting-page": {
|
||||
"label": "स्टार्टिंग पेज",
|
||||
"unset": "अनसेट"
|
||||
},
|
||||
"tray": {
|
||||
"label": "ट्रे",
|
||||
"submenu": {
|
||||
"disabled": "बंद किया गया है",
|
||||
"enabled-and-hide-app": "सक्रिय है और एप छुपाएं",
|
||||
"enabled-and-show-app": "सक्रिय है और एप दिखाएं",
|
||||
"play-pause-on-click": "क्लिक पर प्ले/पोज"
|
||||
}
|
||||
},
|
||||
"visual-tweaks": {
|
||||
"label": "दृश्य परिवर्तन",
|
||||
"submenu": {
|
||||
"like-buttons": {
|
||||
"default": "डिफॉल्ट",
|
||||
"force-show": "बल पूर्वक दिखाएं",
|
||||
"hide": "छुपाएं",
|
||||
"label": "लाइक बटंस"
|
||||
},
|
||||
"remove-upgrade-button": "अपग्रेड बटन हटाएं",
|
||||
"theme": {
|
||||
"dialog": {
|
||||
"button": {
|
||||
"cancel": "रद्द करें",
|
||||
"remove": "हटाएं"
|
||||
},
|
||||
"remove-theme": "क्या आप निश्चित है आपको कस्टम थीम हटानी है?",
|
||||
"remove-theme-message": "यह कस्टम थीम को हटा देगा"
|
||||
},
|
||||
"label": "थीम",
|
||||
"submenu": {
|
||||
"import-css-file": "कस्टम सीएसएस फाइल को आयात करें",
|
||||
"no-theme": "कोई थीम नही"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "सक्रिय",
|
||||
"label": "प्लगिंस",
|
||||
"new": "नया"
|
||||
},
|
||||
"view": {
|
||||
"label": "देखें",
|
||||
"submenu": {
|
||||
"force-reload": "बल पूर्वक रिलोड करें",
|
||||
"reload": "रिलोड करें",
|
||||
"reset-zoom": "वास्तविक आकार",
|
||||
"toggle-fullscreen": "टागल फुल स्क्रीन",
|
||||
"zoom-in": "ज़ूम इन",
|
||||
"zoom-out": "ज़ूम आउट"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"next": "अगला",
|
||||
"play-pause": "चलाएँ/रोकें",
|
||||
"previous": "पिछला",
|
||||
"quit": "निकास",
|
||||
"restart": "ऐप पुनः प्रारंभ करें",
|
||||
"show": "ऐप दिखाए",
|
||||
"tooltip": {
|
||||
"default": "यूट्यूब म्यूजिक",
|
||||
"with-song-info": "यूट्यूब म्यूजिक: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "यदि कोई विज्ञापन चलता है तो यह ऑडियो को म्यूट कर देता है और प्लेबैक गति 16x पर सेट कर देता है",
|
||||
"name": "विज्ञापन की गति बढ़ाना"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "डिफ़ॉल्ट रूप से सभी विज्ञापनों और ट्रैकिंग को ब्लॉक करें",
|
||||
"menu": {
|
||||
"blocker": "ब्लॉकर"
|
||||
},
|
||||
"name": "विज्ञापन अवरोधक"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "प्लेलिस्ट या एल्बम के सभी गानों पर लागू करने के लिए \"अंडिसलाइक,\" \"डिसलाइक,\" \"लाइक,\" और \"अनलाइक\" बटन जोड़ता है",
|
||||
"name": "एल्बम एक्शन"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "एल्बम रंग पैलेट के आधार पर एक गतिशील थीम और दृश्य प्रभाव लागू करता है",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "रंग मिश्रण अनुपात",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "एल्बम रंग थीम"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "वीडियो से हल्के रंगों को आपकी स्क्रीन की पृष्ठभूमि में डालकर एक प्रकाश प्रभाव लागू करता है",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "धुंधलापन मात्रा",
|
||||
"submenu": {
|
||||
"pixels": "{{blurAmount}} पिक्सल"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"label": "बफर",
|
||||
"submenu": {
|
||||
"buffer": "{{buffer}}"
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"label": "अस्पष्टता",
|
||||
"submenu": {
|
||||
"percent": "{{opacity}}%"
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "गुणवत्ता",
|
||||
"submenu": {
|
||||
"pixels": "{{quality}} पिक्सल"
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"label": "माप",
|
||||
"submenu": {
|
||||
"percent": "{{size}}%"
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"label": "चिकनाई संक्रमण"
|
||||
}
|
||||
}
|
||||
},
|
||||
"video-toggle": {
|
||||
"menu": {
|
||||
"align": {
|
||||
"submenu": {
|
||||
"left": "बाएं",
|
||||
"middle": "मध्य",
|
||||
"right": "दाहिने"
|
||||
}
|
||||
},
|
||||
"force-hide": "वीडियो टैब को बलपूर्वक हटाएं",
|
||||
"mode": {
|
||||
"label": "तरीका"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,12 +2,12 @@
|
||||
"common": {
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "Nem sikerült futtatni a plugint {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "Plugin {{pluginName}}::{{contextName}} a {{ms}}ms időpontban lefutott",
|
||||
"initialize-failed": "Nem sikerült inicializálni a \"{{pluginName}}\" plugint",
|
||||
"execute-failed": "Nem sikerült futtatni a bővítményt {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "A {{pluginName}}::{{contextName}} bővítmény végrehajtva {{ms}} ms alatt",
|
||||
"initialize-failed": "Nem sikerült inicializálni a \"{{pluginName}}\" bővítményt",
|
||||
"load-all": "Összes bővítmény betöltése",
|
||||
"load-failed": "Nem sikerült betölteni a \"{{pluginName}}\" plugint",
|
||||
"loaded": "\"{{pluginName}}\" plugin betöltve",
|
||||
"load-failed": "Nem sikerült betölteni a \"{{pluginName}}\" bővítményt",
|
||||
"loaded": "\"{{pluginName}}\" bővítmény betöltve",
|
||||
"unload-failed": "Nem sikerült a \"{{pluginName}}\" bővítményt kikapcsolni",
|
||||
"unloaded": "A \"{{pluginName}}\" bővítmény kikapcsolva"
|
||||
}
|
||||
@ -33,13 +33,13 @@
|
||||
"css-file-not-found": "CSS fájl \"{{cssFile}}\" nem létezik, figyelmen kívül hagyva"
|
||||
},
|
||||
"unresponsive": {
|
||||
"details": "Nem reagál hiba!\n{{error}}"
|
||||
"details": "Nem válaszol!\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "Alkalmazás gyorsítótárának törlése"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "Az ablak a képernyőn kívül próbált betölteni, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||
"tried-to-render-offscreen": "Az ablak a képernyőn kívül próbált betölteni, ablakMéret={{windowSize}}, kijelzőMéret={{displaySize}}, pozíció={{position}}"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
@ -53,8 +53,8 @@
|
||||
"later": "Később",
|
||||
"restart-now": "Újraindítás most"
|
||||
},
|
||||
"detail": "A \"{{pluginName}}\" plugin újraindítást igényel a bekapcsoláshoz",
|
||||
"message": "\"{{pluginName}}\" nevű plugin-t újra kell indítani",
|
||||
"detail": "A \"{{pluginName}}\" bővítmény bekapcsolása az alkalmazás újraindítását igényli",
|
||||
"message": "\"{{pluginName}}\" nevű bővítményt újra kell indítani",
|
||||
"title": "Újraindítás szükséges"
|
||||
},
|
||||
"unresponsive": {
|
||||
@ -73,7 +73,7 @@
|
||||
"download": "Letöltés",
|
||||
"ok": "OK"
|
||||
},
|
||||
"detail": "Az új verzió elérhető, és letölthető az alábbi linken {{downloadLink}}",
|
||||
"detail": "Új verzió elérhető, amely letölthető az alábbi linken {{downloadLink}}",
|
||||
"message": "Új verzió áll rendelkezésre",
|
||||
"title": "Frissítés elérhető"
|
||||
}
|
||||
@ -96,7 +96,7 @@
|
||||
"advanced-options": {
|
||||
"label": "Speciális beállítások",
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "Az alkalmazás gyorsítótárának törlése indításkor",
|
||||
"auto-reset-app-cache": "Alkalmazás gyorsítótárának törlése indításkor",
|
||||
"disable-hardware-acceleration": "Hardveres gyorsítás kikapcsolása",
|
||||
"edit-config-json": "config.json szerkesztése",
|
||||
"override-user-agent": "Kliens felülírása",
|
||||
@ -123,38 +123,38 @@
|
||||
},
|
||||
"language": {
|
||||
"dialog": {
|
||||
"message": "Az Újraindítást követően változik meg a nyelv",
|
||||
"title": "Megváltozott a nyelv"
|
||||
"message": "A nyelv az allkalmazás újraindítása után megváltozik",
|
||||
"title": "Nyelv megváltoztatva"
|
||||
},
|
||||
"label": "Nyelv",
|
||||
"submenu": {
|
||||
"to-help-translate": "Szeretne a fordításban segíteni? Kattintson ide"
|
||||
"to-help-translate": "Szeretnél segíteni a fordításban? Kattints ide"
|
||||
}
|
||||
},
|
||||
"resume-on-start": "Indításkor az utolsó zene folytatása",
|
||||
"single-instance-lock": "Csak egy példány engedélyezése",
|
||||
"resume-on-start": "Zene folytatása az alkalmazás indításakor",
|
||||
"single-instance-lock": "Csak egy példány",
|
||||
"start-at-login": "Futtatás rendszerindításkor",
|
||||
"starting-page": {
|
||||
"label": "Indítási hely",
|
||||
"label": "Induláskor",
|
||||
"unset": "Visszaállítás"
|
||||
},
|
||||
"tray": {
|
||||
"label": "Tálca",
|
||||
"label": "Tálca ikon",
|
||||
"submenu": {
|
||||
"disabled": "Letiltva",
|
||||
"enabled-and-hide-app": "Aktív és az alkalmazás elrejtve",
|
||||
"enabled-and-show-app": "Aktív és az alkalmazás megjelenítve",
|
||||
"enabled-and-hide-app": "Engedélyezve és alkalmazás elrejtése",
|
||||
"enabled-and-show-app": "Engedélyezve és alkalmazás megjelenítése",
|
||||
"play-pause-on-click": "Lejátszás/Szünet az ikonra kattintással"
|
||||
}
|
||||
},
|
||||
"visual-tweaks": {
|
||||
"label": "Kinézeti beállítások",
|
||||
"label": "Megjelenési beállítások",
|
||||
"submenu": {
|
||||
"like-buttons": {
|
||||
"default": "Alapértelmezett",
|
||||
"force-show": "Megjelenítés kényszerítése",
|
||||
"hide": "Elrejtése",
|
||||
"label": "Kedvelés gombok"
|
||||
"label": "Reakció gombok"
|
||||
},
|
||||
"remove-upgrade-button": "Előfizetés gombjának eltávolítása",
|
||||
"theme": {
|
||||
@ -163,8 +163,8 @@
|
||||
"cancel": "Mégse",
|
||||
"remove": "Eltávolít"
|
||||
},
|
||||
"remove-theme": "Biztos, hogy el akarja távolítani az egyéni témát?",
|
||||
"remove-theme-message": "Ez el fogja távolítani az egyéni témát"
|
||||
"remove-theme": "Biztos, hogy el szeretnéd távolítani az egyéni témát?",
|
||||
"remove-theme-message": "Ez eltávolítja az egyéni témát"
|
||||
},
|
||||
"label": "Téma",
|
||||
"submenu": {
|
||||
@ -186,10 +186,10 @@
|
||||
"submenu": {
|
||||
"force-reload": "Kényszerített újratöltés",
|
||||
"reload": "Újratöltés",
|
||||
"reset-zoom": "Valós méret",
|
||||
"reset-zoom": "Alapértelmezett méret visszaállítása",
|
||||
"toggle-fullscreen": "Teljes képernyő be/ki",
|
||||
"zoom-in": "Nagyítás",
|
||||
"zoom-out": "Kicsinyítés"
|
||||
"zoom-in": "Szöveg nagyítása",
|
||||
"zoom-out": "Szöveg kicsinyítése"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -208,25 +208,25 @@
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Ha reklám szól, elnémítja a hangot és a lejátszási sebességet 16x-ra állítja",
|
||||
"name": "Gyorsítás hozzáadása"
|
||||
"description": "Ha egy hirdetés elindul, elnémítja a hangot, és a lejátszási sebességet 16x-ra állítja",
|
||||
"name": "Hirdetésgyorsítás"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Alapértelmezés szerint blokkolja az összes hirdetést és nyomkövetést",
|
||||
"description": "Alapértelmezetten minden hirdetés és nyomkövetés blokkolása",
|
||||
"menu": {
|
||||
"blocker": "Blokkoló"
|
||||
"blocker": "Blokkolási módszer"
|
||||
},
|
||||
"name": "Reklámblokkoló"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Dislike, Undislike, Like, Unlike gombok hozzáadása, amivel ezt a lejátszási listán vagy albumon lévő összes dalra alkalmazza",
|
||||
"description": "Hozzáadja a Tetszik, Nem tetszik és ezek visszavonására szolgáló gombokat, hogy ezeket az összes dalra alkalmazhasd egy lejátszási listán vagy albumban",
|
||||
"name": "Album műveletek"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "Dinamikus téma és vizuális effektek alkalmazása az album színpalettája alapján",
|
||||
"description": "Dinamikus témát és vizuális effekteket alkalmaz az album színpalettája alapján",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Szín keverés aránya",
|
||||
"label": "Színkeverés mértéke",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
@ -235,12 +235,12 @@
|
||||
"name": "Album színtéma"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "Fényhatás alkalmazása a videóból származó lágy színek vetítésével a képernyő hátterére",
|
||||
"description": "Fényhatás effektust alkalmaz, amely a videóból származó lágy színeket vetíti a képernyő hátterére",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "Elmosódás mértéke",
|
||||
"submenu": {
|
||||
"pixels": "{{blurAmount}} képpontok"
|
||||
"pixels": "{{blurAmount}} pixel"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
@ -258,7 +258,7 @@
|
||||
"quality": {
|
||||
"label": "Minőség",
|
||||
"submenu": {
|
||||
"pixels": "{{quality}} képpont"
|
||||
"pixels": "{{quality}} pixel"
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
@ -270,14 +270,64 @@
|
||||
"smoothness-transition": {
|
||||
"label": "Sima átmenet",
|
||||
"submenu": {
|
||||
"during": "{{interpolationTime}}s alatt"
|
||||
"during": "{{interpolationTime}} másodperc alatt"
|
||||
}
|
||||
},
|
||||
"use-fullscreen": {
|
||||
"label": "Teljes képernyő használata"
|
||||
}
|
||||
},
|
||||
"name": "Természetes mód"
|
||||
"name": "Ambient mód"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "Hozzáadja a YouTube Music támogatását az Amuse \"now playing\" widgethez a 6K Labs által",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Az Amuse API szerver fut. Használja a GET /query kérést a dalinformációk lekéréséhez."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Hozzáad egy API szervert a lejátszó vezérléséhez",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Engedélyez",
|
||||
"deny": "Megtagad"
|
||||
},
|
||||
"message": "Engedélyezi, hogy {{ID}} ({{origin}}) hozzáférjen az API-hoz?",
|
||||
"title": "API-hozzáférési kérelem"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Engedélyezési módszer",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Engedélyezés az első kérésnél"
|
||||
},
|
||||
"none": {
|
||||
"label": "Nincs engedélyezés"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Kiszolgáló név"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port"
|
||||
}
|
||||
},
|
||||
"name": "API szerver [Béta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Adja meg az API szerver kiszolgáló nevét (például 0.0.0.0):",
|
||||
"title": "Kiszolgáló neve"
|
||||
},
|
||||
"port": {
|
||||
"label": "Adja meg az API szerver portját:",
|
||||
"title": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"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)",
|
||||
@ -325,7 +375,7 @@
|
||||
"fade-in-duration": "Áttünés időtartama (ms)",
|
||||
"fade-out-duration": "Fokozatos halkítás időtartama (ms)",
|
||||
"fade-scaling": {
|
||||
"label": "Áttünés értéke",
|
||||
"label": "Áttünés mértéke",
|
||||
"linear": "Lineáris",
|
||||
"logarithmic": "Logaritmikus"
|
||||
},
|
||||
@ -336,7 +386,7 @@
|
||||
}
|
||||
},
|
||||
"disable-autoplay": {
|
||||
"description": "A Zenék nem fognak maguktól elindulni, a bővítmény használata során kézileg kel indítani a zenéket",
|
||||
"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": {
|
||||
"apply-once": "Csak induláskor alkalmazza"
|
||||
},
|
||||
@ -356,7 +406,7 @@
|
||||
"connected": "Kapcsolódva",
|
||||
"disconnected": "Nincs Kapcsolódva",
|
||||
"hide-duration-left": "Hátralévő idő elrejtése",
|
||||
"hide-github-button": "GitHub link gombjának 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"
|
||||
},
|
||||
@ -418,7 +468,8 @@
|
||||
"label": "Letöltés befejezéskor",
|
||||
"prompt": {
|
||||
"last-percent": "x százalék után",
|
||||
"last-seconds": "Utolsó x másodperc"
|
||||
"last-seconds": "Utolsó x másodperc",
|
||||
"title": "Letöltés idejének beállítása"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "Speciális",
|
||||
@ -440,6 +491,18 @@
|
||||
"button": "Letöltés"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Hangszínszabályzót ad hozzá a zenelejátszóhoz",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Hangprofil",
|
||||
"list": {
|
||||
"bass-booster": "Basszuskiemelés"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Hangszínszabályzó"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Az hangerő csúszka exponenciálissá tételével könnyebbé válik az alacsony hangerő kiválasztása.",
|
||||
"name": "Exponenciális hangerő"
|
||||
@ -488,6 +551,7 @@
|
||||
"host-only": "Csak a házigazda tudja vezérelni a lejátszási listát és a lejátszót",
|
||||
"playlist": "Engedélyezi a vendégeknek a lejátszási lista vezérlését"
|
||||
},
|
||||
"set-permission": "Vezérlési engedély módosítása",
|
||||
"status": {
|
||||
"disconnected": "Kapcsolat bontva",
|
||||
"guest": "Csatlakozva vendégként",
|
||||
@ -560,7 +624,7 @@
|
||||
}
|
||||
},
|
||||
"playback-speed": {
|
||||
"description": "Hallgass gyorsan, hallgass lassan! Hozzáad egy csúszkát, amely szabályozza a dal sebességét",
|
||||
"description": "Hallgassd gyorsan, hallgassd lassan! Hozzáad egy csúszkát, amely szabályozza a dal sebességét",
|
||||
"name": "Lejátszás sebessége",
|
||||
"templates": {
|
||||
"button": "Sebesség"
|
||||
@ -618,8 +682,10 @@
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "Add meg a ListenBrainz felhasználói tokenedet"
|
||||
}
|
||||
},
|
||||
"scrobble-other-media": "Más média scrobbelése"
|
||||
},
|
||||
"name": "Scrobbler",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "Last.fm API kulcs",
|
||||
@ -667,19 +733,47 @@
|
||||
"synced-lyrics": {
|
||||
"description": "Szinkronizált dalszövegeket biztosít dalokhoz, LRClib-hez hasonló szolgáltatókat használva.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Hiba történt a dalszövegek lekérése közben. Kérlek, próbáld újra később.",
|
||||
"not-found": "⚠️ - Nem található dalszöveg ehhez a zenéhez."
|
||||
"fetch": "⚠️\tHiba történt a dalszöveg lekérése közben.\n\tKérjük, próbálja meg később újra.",
|
||||
"not-found": "⚠️ - Ehhez a dalhoz nem található dalszöveg."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Alapértelmezett karakter a dalszövegek között",
|
||||
"tooltip": "Válassza ki az alapértelmezett karaktert, amelyet a dalszövegek közötti szünethez használni szeretne"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Soreffekt",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "Díszes",
|
||||
"tooltip": "Használj nagy, alkalmazásszerű effektusokat az aktuális sorhoz"
|
||||
},
|
||||
"focus": {
|
||||
"label": "Fókuszált",
|
||||
"tooltip": "Az aktuális sor kijelőlése fehérrel"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Eltolás",
|
||||
"tooltip": "Az aktuális sort jobbra tolja. (mintha tabulálnád)"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Mérték"
|
||||
"label": "Méretezett",
|
||||
"tooltip": "Az aktuális sort kissé nagyobbra méretezi, kiemelve azt a többi sor közül"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tooltip": "Válassza ki az aktuális sorra alkalmazandó effektust"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Dalszöveg tökéletes szinkronizálása"
|
||||
"label": "Dalszöveg tökéletes szinkronizálása",
|
||||
"tooltip": "Számítsa ki az aktuális sor megjelenítésének idejét ezredmásodperc pontossággal (ez kis mértékben befolyásolhatja a teljesítményt)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Pontatlan időzítésű dalszövegek megjelenítése",
|
||||
"tooltip": "Ha a dalt nem találja, a bővítmény újra próbálkozik egy másik keresési lekérdezéssel.\nAz eredmény a második próbálkozás után nem biztos, hogy pontos lesz."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Időkódok megjelenítése",
|
||||
"tooltip": "Az időkódok megjelenítése a dalszövegek mellett"
|
||||
}
|
||||
},
|
||||
"name": "Szinkronizált dalszövegek",
|
||||
|
||||
@ -202,7 +202,7 @@
|
||||
"show": "Tampilkan jendela",
|
||||
"tooltip": {
|
||||
"default": "YouTube Musik",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
"with-song-info": "YouTube Musik: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -279,6 +279,56 @@
|
||||
},
|
||||
"name": "Mode ambient"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "Menambahkan dukungan YouTube Music untuk widget Amuse yang sedang diputar oleh 6K Labs",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Server API Amuse sedang berjalan. GET /query untuk mendapatkan info lagu."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Menambahkan server API untuk mengontrol pemutar",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Izinkan",
|
||||
"deny": "Menolak"
|
||||
},
|
||||
"message": "Izinkan {{ID}} ({{origin}}) untuk mengakses API?",
|
||||
"title": "Permintaan otorisasi API"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Strategi otorisasi",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Otorisasi pada permintaan pertama"
|
||||
},
|
||||
"none": {
|
||||
"label": "Tidak ada otorisasi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Nama host"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port"
|
||||
}
|
||||
},
|
||||
"name": "API Server [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Masukkan nama host (seperti 0.0.0.0) untuk server API:",
|
||||
"title": "Nama host"
|
||||
},
|
||||
"port": {
|
||||
"label": "Masukkan port untuk server API:",
|
||||
"title": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Menerapkan kompresi pada audio (mengurangi volume pada bagian paling keras dari sinyal dan meningkatkan volume pada bagian paling lembut)",
|
||||
"name": "Kompresi suara"
|
||||
@ -441,6 +491,18 @@
|
||||
"button": "Unduh"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Menambahkan equalizer ke pemutar",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Prasetel",
|
||||
"list": {
|
||||
"bass-booster": "Penguat Bass"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Equalizer"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Buat penggeser volume menjadi eksponen sehingga memudahkan memilih volume yang lebih rendah.",
|
||||
"name": "Volume Eksponen"
|
||||
@ -671,8 +733,8 @@
|
||||
"synced-lyrics": {
|
||||
"description": "Menyediakan lirik lagu yang disinkronkan, menggunakan penyedia seperti LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Terjadi kesalahan saat mengambil lirik. Coba lagi nanti.",
|
||||
"not-found": "⚠️ - Tidak ada lirik yang ditemukan untuk lagu ini."
|
||||
"fetch": "⚠️\tTerjadi kesalahan saat mengambil lirik.\n\tSilakan coba lagi nanti.",
|
||||
"not-found": "⚠️ Tidak ada lirik yang ditemukan untuk lagu ini."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
@ -682,6 +744,10 @@
|
||||
"line-effect": {
|
||||
"label": "Efek garis",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "Mewah",
|
||||
"tooltip": "Gunakan efek besar seperti aplikasi pada baris saat ini"
|
||||
},
|
||||
"focus": {
|
||||
"label": "Fokus",
|
||||
"tooltip": "Jadikan hanya baris saat ini berwarna putih"
|
||||
|
||||
@ -279,6 +279,49 @@
|
||||
},
|
||||
"name": "Umhverfishamur"
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Bætir API netþjóni til að stjórna spilaranum",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Leyfa",
|
||||
"deny": "Óleyfa"
|
||||
},
|
||||
"message": "Leyfa {{ID}} ({{origin}}) að aðganga API-ið?",
|
||||
"title": "API heimildarbeiðni"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Heimildarstefna",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Heimila á fyrst beiðni"
|
||||
},
|
||||
"none": {
|
||||
"label": "Nei heimild"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Hýsitölvunafn"
|
||||
},
|
||||
"port": {
|
||||
"label": "Tengi"
|
||||
}
|
||||
},
|
||||
"name": "API-Netþjónn [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Sláðu inn hýsitölvunafnið (eins og 0.0.0.0) fyrir API-netþjónninn:",
|
||||
"title": "Hýsitölvunafn"
|
||||
},
|
||||
"port": {
|
||||
"label": "Sláðu inn tengið fyrir API-netþjónninn:",
|
||||
"title": "Tengi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Notaðu þjöppun á hljóð (lækkar hljóðstyrk háværustu hluta merkis og hækkar hljóðstyrk í mýkstu hlutunum)",
|
||||
"name": "Hljóðþjöppu"
|
||||
@ -670,7 +713,56 @@
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Veitir samstillta texta við lög, með því að nota veitur eins og LRClib.",
|
||||
"name": "Samstilltur texti"
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Villa kom upp við að sækja textann. Vinsamlegast reyndu aftur síðar.",
|
||||
"not-found": "⚠️ - Enginn texti fannst við þetta lag."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Sjálfgefið tákn á milli texta",
|
||||
"tooltip": "Veldu sjálfgefna tákn til að nota fyrir bilið á milli texta"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Línuafleiðing",
|
||||
"submenu": {
|
||||
"focus": {
|
||||
"label": "Brennidepill",
|
||||
"tooltip": "Gerðu aðeins núverandi línu hvíta"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Fararbyrjun",
|
||||
"tooltip": "Fararbyrjun á hægri af núverandi línan"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Skali",
|
||||
"tooltip": "Skala núverandi línu"
|
||||
}
|
||||
},
|
||||
"tooltip": "Veldu áhrif til að nota á núverandi línu"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Gera textana fullkomlega samstillta",
|
||||
"tooltip": "Reikna upp á millisekúndu birtingu næstu línu (getur haft lítil áhrif á frammistöðu)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Sýna texta, jafnvel þótt hann sé ónákvæmur",
|
||||
"tooltip": "Ef lagið finnst ekki reynir tengiforritið aftur með annarri leitarfyrirspurn.\nNiðurstaðan úr annarri tilraun er kannski ekki nákvæm."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Sýna tímikóðar",
|
||||
"tooltip": "Sýna tímakóðana við hliðina á textanum"
|
||||
}
|
||||
},
|
||||
"name": "Samstilltur texti",
|
||||
"refetch-btn": {
|
||||
"fetching": "Er að sækja",
|
||||
"normal": "Endursækja texta"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - Textarnir gætu verið ekki samstilltir vegna tímalengdar.",
|
||||
"inexact": "⚠️ - Textinn við þetta lag er kannski ekki nákvæmur",
|
||||
"instrumental": "⚠️ - Þetta er hljóðfærilegt lag"
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Stjórnaðu spilun frá Windows verkefnastikunni þinni",
|
||||
|
||||
@ -279,6 +279,56 @@
|
||||
},
|
||||
"name": "Modalità Ambiente"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "Aggiunge il supporto a YouTube Music per il widget Amuse Now Playing di 6K Labs",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Il server API di Amuse è in funzione. GET /query per ottenere informazioni sui brani."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Aggiunge un server API per controllare il player",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Permetti",
|
||||
"deny": "Nega"
|
||||
},
|
||||
"message": "Consentire a {{ID}} ({{origin}}) di accedere all'API?",
|
||||
"title": "Autorizzazione API richiesta"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Metodo di autorizzazione",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Autorizza alla prima richiesta"
|
||||
},
|
||||
"none": {
|
||||
"label": "Nessuna autorizzazione"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"label": "Porta"
|
||||
}
|
||||
},
|
||||
"name": "API Server [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Inserisci il nome host (ad esempio 0.0.0.0) per il server API:",
|
||||
"title": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"label": "Inserisci la porta per il server API:",
|
||||
"title": "Porta"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Attiva la compressione audio (abbassa il volume delle parti più alte e alza quello delle parti più basse del segnale)",
|
||||
"name": "Compressore audio"
|
||||
@ -441,6 +491,18 @@
|
||||
"button": "Scarica"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Aggiunge un equalizzatore al player",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Presets",
|
||||
"list": {
|
||||
"bass-booster": "Booster dei bassi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Equalizzatore"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Rende esponenziale il cursore del volume, in modo da facilitare la selezione di volumi più bassi.",
|
||||
"name": "Volume esponenziale"
|
||||
|
||||
@ -279,6 +279,49 @@
|
||||
},
|
||||
"name": "アンビエント モード"
|
||||
},
|
||||
"api-server": {
|
||||
"description": "プレイヤーを制御するAPIサーバーを追加",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "許可",
|
||||
"deny": "拒否"
|
||||
},
|
||||
"message": "{{ID}}が{{origin}}にアクセスすることを許可しますか?",
|
||||
"title": "API承認リクエスト"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "許可方法",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "初回リクエスト時に承認"
|
||||
},
|
||||
"none": {
|
||||
"label": "不許可"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "ホスト名"
|
||||
},
|
||||
"port": {
|
||||
"label": "ポート"
|
||||
}
|
||||
},
|
||||
"name": "APIサーバー(Beta)",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "APIサーバーのポート名(0.0.0.0など)を入力:",
|
||||
"title": "ホスト名"
|
||||
},
|
||||
"port": {
|
||||
"label": "APIサーバーのポートを入力:",
|
||||
"title": "ポート"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "オーディオにコンプレッサーを適用します(信号での一番大きい部分の音量を下げ、小さい部分の音量を上げる)",
|
||||
"name": "オーディオコンプレッサー"
|
||||
@ -441,6 +484,18 @@
|
||||
"button": "ダウンロード"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "プレイヤーにイコライザーを追加",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "プリセット",
|
||||
"list": {
|
||||
"bass-booster": "ベースブースター"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "イコライザー"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "音量スライダを指数関数的にさせ、低い音量に設定しやすくなります。",
|
||||
"name": "指数音量"
|
||||
|
||||
274
src/i18n/resources/ka.json
Normal file
@ -0,0 +1,274 @@
|
||||
{
|
||||
"language": {
|
||||
"code": "ka",
|
||||
"local-name": "ქართული",
|
||||
"name": "Georgian"
|
||||
},
|
||||
"main": {
|
||||
"dialog": {
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "მოგვიანებით"
|
||||
}
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "გასვლა",
|
||||
"relaunch": "თავიდან გაშვება",
|
||||
"wait": "მოცდა"
|
||||
}
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
"download": "გადმოწერა",
|
||||
"ok": "დიახ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"about": "შესახებ",
|
||||
"navigation": {
|
||||
"label": "ნავიგაცია",
|
||||
"submenu": {
|
||||
"quit": "გასვლა"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"label": "მორგება",
|
||||
"submenu": {
|
||||
"language": {
|
||||
"label": "ენა"
|
||||
},
|
||||
"starting-page": {
|
||||
"unset": "მოხსნა"
|
||||
},
|
||||
"tray": {
|
||||
"submenu": {
|
||||
"disabled": "გამორთულია"
|
||||
}
|
||||
},
|
||||
"visual-tweaks": {
|
||||
"submenu": {
|
||||
"like-buttons": {
|
||||
"default": "ნაგულისხმევი",
|
||||
"hide": "დამალვა"
|
||||
},
|
||||
"theme": {
|
||||
"dialog": {
|
||||
"button": {
|
||||
"cancel": "გაუქმება",
|
||||
"remove": "წაშლა"
|
||||
}
|
||||
},
|
||||
"label": "თემა"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "ჩართულია",
|
||||
"label": "დამატებები",
|
||||
"new": "ახალი"
|
||||
},
|
||||
"view": {
|
||||
"label": "ხედი",
|
||||
"submenu": {
|
||||
"reload": "თავიდან ჩატვირთვა"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"next": "შემდეგი",
|
||||
"play-pause": "დაკვრა/შეჩერება",
|
||||
"previous": "წინა",
|
||||
"quit": "გასვლა"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"adblocker": {
|
||||
"menu": {
|
||||
"blocker": "დამბლოკავი"
|
||||
}
|
||||
},
|
||||
"album-color-theme": {
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ambient-mode": {
|
||||
"menu": {
|
||||
"buffer": {
|
||||
"label": "ბუფერი",
|
||||
"submenu": {
|
||||
"buffer": "{{buffer}}"
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"label": "გაუმჭვირვალობა",
|
||||
"submenu": {
|
||||
"percent": "{{opacity}}%"
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "ხარისხი"
|
||||
},
|
||||
"size": {
|
||||
"label": "ზომა",
|
||||
"submenu": {
|
||||
"percent": "{{size}}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"amuse": {
|
||||
"name": "Amuse"
|
||||
},
|
||||
"api-server": {
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "დაშვება",
|
||||
"deny": "აკრძალვა"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hostname": {
|
||||
"label": "ჰოსტის სახელი"
|
||||
},
|
||||
"port": {
|
||||
"label": "პორტი"
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"title": "ჰოსტის სახელი"
|
||||
},
|
||||
"port": {
|
||||
"title": "პორტი"
|
||||
}
|
||||
}
|
||||
},
|
||||
"captions-selector": {
|
||||
"prompt": {
|
||||
"selector": {
|
||||
"none": "არცერთი"
|
||||
}
|
||||
}
|
||||
},
|
||||
"crossfade": {
|
||||
"menu": {
|
||||
"advanced": "დამატებით"
|
||||
},
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
"fade-scaling": {
|
||||
"linear": "წრფივი",
|
||||
"logarithmic": "ლოგარითმული"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"discord": {
|
||||
"menu": {
|
||||
"connected": "დაკავშირებული",
|
||||
"disconnected": "გათიშული"
|
||||
}
|
||||
},
|
||||
"downloader": {
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"error": {
|
||||
"buttons": {
|
||||
"ok": "დიახ"
|
||||
}
|
||||
},
|
||||
"start-download-playlist": {
|
||||
"buttons": {
|
||||
"ok": "დიახ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"converting": "გადაყვანა…",
|
||||
"downloading": "გადმოწერა…",
|
||||
"loading": "ჩატვირთვა…",
|
||||
"saving": "შენახვა…"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"download-finish-settings": {
|
||||
"submenu": {
|
||||
"advanced": "დამატებით",
|
||||
"enabled": "ჩართულია",
|
||||
"percent": "პროცენტი",
|
||||
"seconds": "წამი"
|
||||
}
|
||||
},
|
||||
"presets": "პრესეტი"
|
||||
},
|
||||
"name": "გადმომწერი",
|
||||
"templates": {
|
||||
"button": "გადმოწერა"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"internal": {
|
||||
"save": "შენახვა"
|
||||
},
|
||||
"menu": {
|
||||
"status": {
|
||||
"disconnected": "გათიშული"
|
||||
}
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"name": "ნავიგაცია"
|
||||
},
|
||||
"notifications": {
|
||||
"name": "გაფრთხილებები"
|
||||
},
|
||||
"picture-in-picture": {
|
||||
"name": "სურათი სურათში",
|
||||
"templates": {
|
||||
"button": "სურათი სურათში"
|
||||
}
|
||||
},
|
||||
"playback-speed": {
|
||||
"templates": {
|
||||
"button": "სიჩქარე"
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"prompt": {
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "შემდეგი",
|
||||
"previous": "წინა"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sponsorblock": {
|
||||
"name": "სარეკლამო ბლოკი"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"menu": {
|
||||
"line-effect": {
|
||||
"submenu": {
|
||||
"focus": {
|
||||
"label": "ფოკუსი"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -279,6 +279,13 @@
|
||||
},
|
||||
"name": "앰비언트 모드"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "6K Labs Amuse의 'now playing' 위젯에 YouTube Music 지원 추가",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Amuse API 서버가 실행 중입니다. GET /query로 노래 정보를 가져오세요."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "플레이어를 제어하기 위한 API 서버를 추가합니다",
|
||||
"dialog": {
|
||||
@ -484,6 +491,18 @@
|
||||
"button": "다운로드"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "플레이어에 이퀄라이저를 추가합니다",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "프리셋",
|
||||
"list": {
|
||||
"bass-booster": "베이스 부스터"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "이퀄라이저"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "음량 슬라이더를 지수적으로 만들어 더 낮은 음량을 쉽게 선택할 수 있도록 합니다.",
|
||||
"name": "지수 음량"
|
||||
@ -522,7 +541,7 @@
|
||||
"menu": {
|
||||
"click-to-copy-id": "호스트 아이디 복사",
|
||||
"close": "Music Together 닫기",
|
||||
"connected-users": "연결된 사용자",
|
||||
"connected-users": "연결된 사용자: {{count}}명",
|
||||
"disconnect": "Music Together 연결 끊기",
|
||||
"empty-user": "연결된 사용자 없음",
|
||||
"host": "Music Together 호스트",
|
||||
@ -714,8 +733,8 @@
|
||||
"synced-lyrics": {
|
||||
"description": "LRClib등의 가사 제공자에서 싱크 가사를 불러옵니다.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - 가사를 불러오는 동안 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
|
||||
"not-found": "⚠️ - 이 노래의 가사를 찾을 수 없습니다."
|
||||
"fetch": "⚠️\t가사를 불러오는 동안 오류가 발생했습니다.\n\t나중에 다시 시도해 주세요.",
|
||||
"not-found": "⚠️ 이 노래의 가사를 찾을 수 없습니다."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
@ -725,6 +744,10 @@
|
||||
"line-effect": {
|
||||
"label": "줄 표시 효과",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "예쁘게",
|
||||
"tooltip": "유튜브 뮤직 앱처럼 커다란 효과를 현재 라인에 사용합니다"
|
||||
},
|
||||
"focus": {
|
||||
"label": "포커스",
|
||||
"tooltip": "현재 줄만 하얀색으로 표시"
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
"css-file-not-found": "CSS-bestand \"{{cssFile}}\" bestaat niet, wordt genegeerd"
|
||||
},
|
||||
"unresponsive": {
|
||||
"details": "Onverantwoordelijkheidsfout!\n{{error}}"
|
||||
"details": "Niet-reagerende fout!\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "App-cache wissen"
|
||||
@ -46,7 +46,7 @@
|
||||
"hide-menu-enabled": {
|
||||
"detail": "Menu is verborgen, gebruik 'Alt' om het weer te geven (of 'Escape' als u de In-App Menu gebruikt)",
|
||||
"message": "Menu verbergen is ingeschakeld",
|
||||
"title": "Menu Verbergen Ingeschakeld"
|
||||
"title": "Menu verbergen ingeschakeld"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
@ -65,7 +65,7 @@
|
||||
},
|
||||
"detail": "Het programma reageert niet! Kies wat u wilt doen:",
|
||||
"message": "De applicatie reageert niet",
|
||||
"title": "Venster Niet Reagerend"
|
||||
"title": "Venster reageert niet"
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
@ -250,7 +250,7 @@
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"label": "Dekking",
|
||||
"label": "Transparantie",
|
||||
"submenu": {
|
||||
"percent": "{{opacity}}%"
|
||||
}
|
||||
@ -268,7 +268,7 @@
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"label": "Soepelheid overgang",
|
||||
"label": "Vloeiende overgang",
|
||||
"submenu": {
|
||||
"during": "Tijdens {{interpolationTime}} s"
|
||||
}
|
||||
@ -279,6 +279,56 @@
|
||||
},
|
||||
"name": "Omgevingsmodus"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "Voegt YouTube Music ondersteuning toe voor de Amuse now playing widget van 6K Labs",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Amuse API server loopt. Gebruik /query voor nummer informatie."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Voegt een API server toe om de speler te besturen",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Toestaan",
|
||||
"deny": "Weigeren"
|
||||
},
|
||||
"message": "Sta {{ID}} {{origin}} toegang toe tot de API?",
|
||||
"title": "API autorisatieverzoek"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Autorisatie strategie",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Autoriseer bij eerste verzoek"
|
||||
},
|
||||
"none": {
|
||||
"label": "geen autorisatie"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Hostnaam"
|
||||
},
|
||||
"port": {
|
||||
"label": "Poort"
|
||||
}
|
||||
},
|
||||
"name": "API Server [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Voeg de hostnaam (bv. 0.0.0.0) voor de API server in:",
|
||||
"title": "Hostnaam"
|
||||
},
|
||||
"port": {
|
||||
"label": "Voeg de poort voor de API server in:",
|
||||
"title": "Poort"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Past compressie toe op audio (verlaagt het volume van de luidste delen van het signaal en verhoogt het volume van de zachtste delen)",
|
||||
"name": "Audiocompressor"
|
||||
@ -395,7 +445,7 @@
|
||||
"download-progress": "Downloaden: {{percent}}%",
|
||||
"downloading": "Aan het downloaden…",
|
||||
"downloading-counter": "{{current}}/{{total}} aan het downloaden…",
|
||||
"downloading-playlist": "Afspeellijst \"{{playlistTitle}}\" {{playlistId}} aan het downloaden ({{playlistSize}} liederen)",
|
||||
"downloading-playlist": "Afspeellijst \"{{playlistTitle}}\" {{playlistId}} aan het downloaden ({{playlistSize}} nummers)",
|
||||
"error-while-downloading": "Er is een fout opgetreden tijdens het downloaden van \"{{author}} - {{title}}\": {{error}}",
|
||||
"folder-already-exists": "De map \"{{playlistFolder}}\" bestaat al",
|
||||
"getting-playlist-info": "Afspeellijst informatie ophalen…",
|
||||
@ -441,6 +491,18 @@
|
||||
"button": "Download"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Voegt een equalizer toe aan de speler",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Voorinstellingen",
|
||||
"list": {
|
||||
"bass-booster": "Basversterker"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Equalizer"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Maakt de volumeschuif exponentieel zodat het gemakkelijker is om lagere volumes te selecteren.",
|
||||
"name": "Exponentieel Volume"
|
||||
@ -450,14 +512,14 @@
|
||||
"menu": {
|
||||
"hide-dom-window-controls": "Verberg DOM-vensterbedieningselementen"
|
||||
},
|
||||
"name": "In-App menu"
|
||||
"name": "In-App Menu"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Voegt ondersteuning voor Lumia Stream toe",
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Voegt tekstondersteuning toe voor de meeste nummers",
|
||||
"description": "Voegt songtekstondersteuning toe voor de meeste nummers",
|
||||
"menu": {
|
||||
"romanized-lyrics": "Geromaniseerde Teksten"
|
||||
},
|
||||
@ -527,15 +589,19 @@
|
||||
"interactive-settings": {
|
||||
"label": "Interactieve instellingen",
|
||||
"submenu": {
|
||||
"hide-button-text": "Verberg tekst op de knop",
|
||||
"refresh-on-play-pause": "Herlaad bij het afspelen/pauzeren"
|
||||
"hide-button-text": "Knoptekst verbergen",
|
||||
"refresh-on-play-pause": "Herlaad bij het afspelen/pauzeren",
|
||||
"tray-controls": "Open/Sluit op tray klik"
|
||||
}
|
||||
},
|
||||
"priority": "Meldingprioriteit",
|
||||
"toast-style": "Toast stijl",
|
||||
"unpause-notification": "Laat een notificatie zijn bij het depauzeren"
|
||||
},
|
||||
"name": "Meldingen"
|
||||
},
|
||||
"picture-in-picture": {
|
||||
"description": "Laat de app toe om naar picture-in-picture modus om te schakelen",
|
||||
"menu": {
|
||||
"always-on-top": "Altijd bovenaan",
|
||||
"hotkey": {
|
||||
@ -543,26 +609,226 @@
|
||||
"prompt": {
|
||||
"keybind-options": {
|
||||
"hotkey": "Sneltoets"
|
||||
}
|
||||
},
|
||||
"label": "Kies een sneltoets om tussen picture-in-picture te schakelen",
|
||||
"title": "Picture-in-picture sneltoets"
|
||||
}
|
||||
},
|
||||
"save-window-position": "Sla schermpositie op",
|
||||
"save-window-size": "Sla schermgrootte op"
|
||||
"save-window-size": "Sla schermgrootte op",
|
||||
"use-native-pip": "Gebruik browser ingebouwde PiP"
|
||||
},
|
||||
"name": "Picture-in-picture",
|
||||
"templates": {
|
||||
"button": "Picture-in-picture"
|
||||
}
|
||||
},
|
||||
"video-toggle": {
|
||||
"playback-speed": {
|
||||
"description": "Luister snel, luister langzaam! Voegt een schuifregelaar toe die de muzieksnelheid regelt",
|
||||
"name": "Afspeelsnelheid",
|
||||
"templates": {
|
||||
"button": "Snelheid"
|
||||
}
|
||||
},
|
||||
"precise-volume": {
|
||||
"description": "Regel het volume nauwkeurig met behulp van het muiswiel/sneltoetsen, met een aangepaste HUD en aanpasbare volumestappen",
|
||||
"menu": {
|
||||
"mode": {
|
||||
"submenu": {
|
||||
"disabled": "Uitgeschakeld"
|
||||
"arrows-shortcuts": "Lokale Pijltjestoetsen bediening",
|
||||
"custom-volume-steps": "Stel aangepaste volumestappen in",
|
||||
"global-shortcuts": "Globale sneltoetsen"
|
||||
},
|
||||
"name": "Nauwkeurig volume",
|
||||
"prompt": {
|
||||
"global-shortcuts": {
|
||||
"keybind-options": {
|
||||
"decrease": "Volume verlagen",
|
||||
"increase": "Volume verhogen"
|
||||
},
|
||||
"label": "Kies Globale Volume toetsen:",
|
||||
"title": "Globale Volume toetsen"
|
||||
},
|
||||
"volume-steps": {
|
||||
"label": "Kies Stappen voor volumeverhoging/-verlaging",
|
||||
"title": "Volume Stappen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quality-changer": {
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"quality-changer": {
|
||||
"detail": "Huidige kwaliteit: {{quality}}",
|
||||
"message": "Kies videokwaliteit:",
|
||||
"title": "Kies Videokwaliteit"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Maakt het mogelijk de videokwaliteit te wijzigen met een knop op de video-overlay",
|
||||
"name": "Videokwaliteitwisselaar"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "Ondersteuning voor scrobbling toevoegen (etc. last.fm, Listenbrainz)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Kan niet verifiëren bij Last.fm\nVerberg de pop-up tot de volgende herstart.",
|
||||
"title": "Authenticatie mislukt"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Last.fm API Instellingen"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "Voer het ListenBrainz-gebruikerstoken in"
|
||||
},
|
||||
"scrobble-other-media": "Scrobble andere media"
|
||||
},
|
||||
"name": "Scrobbler",
|
||||
"prompt": {
|
||||
"lastfm": {
|
||||
"api-key": "Last.fm API sleutel",
|
||||
"api-secret": "Last.fm API geheim"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "Voer uw ListenBrainz-gebruikerstoken in:",
|
||||
"title": "ListenBrainz token"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "Voeg een visuele equalizer toe",
|
||||
"shortcuts": {
|
||||
"description": "Maakt het mogelijk algemene sneltoetsen in te stellen voor afspelen (afspelen/pauzeren/volgende/vorige) en het uitschakelen van media-OSD door mediatoetsen te overschrijven, het inschakelen van Ctrl/CMD + F om te zoeken, het inschakelen van Linux MPRIS-ondersteuning voor mediatoetsen en aangepaste sneltoetsen voor gevorderde gebruikers",
|
||||
"menu": {
|
||||
"visualizer-type": "Type visualisator"
|
||||
"override-media-keys": "Media toetsen overschrijven",
|
||||
"set-keybinds": "Stel de algemene songbediening in"
|
||||
},
|
||||
"name": "Snelkoppelingen (& MPRIS)",
|
||||
"prompt": {
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "Volgende",
|
||||
"play-pause": "Afspelen / Pauzeren",
|
||||
"previous": "Vorige"
|
||||
},
|
||||
"label": "Kies globale toetsen voor nummer bediening:",
|
||||
"title": "Globale toetsen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"skip-disliked-songs": {
|
||||
"description": "Slaat disliked nummers over",
|
||||
"name": "Sla disliked nummers over"
|
||||
},
|
||||
"skip-silences": {
|
||||
"description": "Sla automatisch stiltesecties in nummers over",
|
||||
"name": "Stiltes overslaan"
|
||||
},
|
||||
"sponsorblock": {
|
||||
"description": "Slaat automatisch niet-muziekgedeelten over, zoals intro/outro of gedeelten van muziekvideo's waarin het nummer niet wordt afgespeeld",
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Biedt gesynchroniseerde songteksten voor nummers, met behulp van providers zoals LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️\tEr is een fout opgetreden bij het ophalen van de songtekst.\n\tProbeer het later opnieuw.",
|
||||
"not-found": "⚠️ Er is geen songtekst gevonden voor dit nummer."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Standaardteken tussen songteksten",
|
||||
"tooltip": "Kies het standaardteken dat u wilt gebruiken voor de opening tussen de songteksten"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Lijneffect",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "Luxe",
|
||||
"tooltip": "Gebruik grote, app-achtige effecten op de huidige regel"
|
||||
},
|
||||
"focus": {
|
||||
"label": "Focus",
|
||||
"tooltip": "Maak alleen de huidige regel wit"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Offset",
|
||||
"tooltip": "Offset aan de rechterkant van de huidige lijn"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Schaal",
|
||||
"tooltip": "Schaal de huidige regel"
|
||||
}
|
||||
},
|
||||
"tooltip": "Kies het effect dat u op de huidige regel wilt toepassen"
|
||||
},
|
||||
"precise-timing": {
|
||||
"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)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Toon songteksten, zelfs als ze onnauwkeurig zijn",
|
||||
"tooltip": "Als het nummer niet wordt gevonden, probeert de plug-in het opnieuw met een andere zoekopdracht.\nHet resultaat van de tweede poging is mogelijk niet exact."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Toon tijdcodes",
|
||||
"tooltip": "Toon de tijdcodes naast de songtekst"
|
||||
}
|
||||
},
|
||||
"name": "Gesynchroniseerde songteksten",
|
||||
"refetch-btn": {
|
||||
"fetching": "Ophalen...",
|
||||
"normal": "Songteksten opnieuw ophalen"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - De songteksten zijn mogelijk niet synchroon vanwege een niet-overeenkomende duur.",
|
||||
"inexact": "⚠️ - De songtekst van dit nummer is mogelijk niet exact",
|
||||
"instrumental": "⚠️ - Dit is een instrumentaal nummer"
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Bedien het afspelen vanaf uw Windows-taakbalk",
|
||||
"name": "Taakbalk Mediabediening"
|
||||
},
|
||||
"touchbar": {
|
||||
"description": "Voegt een TouchBar-widget toe voor macOS-gebruikers",
|
||||
"name": "TouchBar"
|
||||
},
|
||||
"tuna-obs": {
|
||||
"description": "Integratie met OBS's plug-in Tuna",
|
||||
"name": "Tuna OBS"
|
||||
},
|
||||
"video-toggle": {
|
||||
"description": "Voegt een knop toe om te schakelen tussen de video-/nummermodus. kan optioneel ook het hele videotabblad verwijderen",
|
||||
"menu": {
|
||||
"align": {
|
||||
"label": "Uitlijning",
|
||||
"submenu": {
|
||||
"left": "Links",
|
||||
"middle": "Midden",
|
||||
"right": "Rechts"
|
||||
}
|
||||
},
|
||||
"force-hide": "Forceer het verwijderen van het videotabblad",
|
||||
"mode": {
|
||||
"label": "Modus",
|
||||
"submenu": {
|
||||
"custom": "Aangepaste schakelaar",
|
||||
"disabled": "Uitgeschakeld",
|
||||
"native": "Native schakelaar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Videoschakelaar",
|
||||
"templates": {
|
||||
"button": "Nummer"
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "Voegt een visualisator toe aan de speler",
|
||||
"menu": {
|
||||
"visualizer-type": "Visualisatietype"
|
||||
},
|
||||
"name": "Visualisator"
|
||||
}
|
||||
|
||||
@ -279,6 +279,56 @@
|
||||
},
|
||||
"name": "Tryb otoczenia"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "Wspiera integrację YouTube Music z widgetami Amuse (od 6K Labs)",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Serwer API Amuse działa. Użyj metody GET do /query, aby zdobyć informację o utworze."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Pozwala na kontrolowanie YouTube Music poprzez podłączenie specjalnego serwera API",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Zezwól",
|
||||
"deny": "Odmów"
|
||||
},
|
||||
"message": "Zezwolić {{ID}} (pochodzenie: {{origin}}) na dostęp do API?",
|
||||
"title": "Prośba o autoryzację API"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Strategia autoryzacji",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Autoryzuj przy pierwszej prośbie"
|
||||
},
|
||||
"none": {
|
||||
"label": "Nie autoryzuj"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Nazwa hosta (IP)"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port"
|
||||
}
|
||||
},
|
||||
"name": "YouTube Music API",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Wpisz nazwę hosta (IP, np. 0.0.0.0), który będzie użyty do serwera API:",
|
||||
"title": "Nazwa hosta"
|
||||
},
|
||||
"port": {
|
||||
"label": "Wpisz port, z którego będzie korzystać serwer API:",
|
||||
"title": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Zastosuj kompresję do dźwięku (obniża głośność najgłośniejszych części sygnału i zwiększa głośność najcichszych części)",
|
||||
"name": "Kompresor dźwięku"
|
||||
@ -441,6 +491,18 @@
|
||||
"button": "Pobierz"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Dodaje equalizer do odtwarzacza",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Presety",
|
||||
"list": {
|
||||
"bass-booster": "Wzmacniacz basu"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Korektor"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Sprawia, że suwak głośności jest proporcjonalna, dzięki czemu łatwiej jest wybrać niższą głośność.",
|
||||
"name": "Proporcjonalna głośność"
|
||||
@ -671,8 +733,8 @@
|
||||
"synced-lyrics": {
|
||||
"description": "Dodaje zsynchronizowane napisy do utworów używając między innymi LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Wystąpił błąd podczas pobierania tekstu utworu. Spróbuj ponownie później.",
|
||||
"not-found": "⚠️ - Nie znaleziono napisów dla tego utworu."
|
||||
"fetch": "⚠️\tWystąpił błąd podczas pobierania tekstu utworu.\n\tSpróbuj ponownie później.",
|
||||
"not-found": "⚠️ Nie znaleziono napisów dla tego utworu."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
@ -682,6 +744,10 @@
|
||||
"line-effect": {
|
||||
"label": "Efekty linijki",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "Facy",
|
||||
"tooltip": "Użyj specjalnych efektów w stylu aplikacji na obecną linię"
|
||||
},
|
||||
"focus": {
|
||||
"label": "Fokus",
|
||||
"tooltip": "Spraw, aby tylko obecna linijka była biała"
|
||||
|
||||
@ -279,6 +279,13 @@
|
||||
},
|
||||
"name": "Modo ambiente"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "Adiciona suporte ao YouTube Music ao widget 'Reproduzindo agora' do Amuse da 6K Labs",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Servidor API do Amuse em execução. GET /query para obter informações da música."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Adiciona um servidor API para controlar o player",
|
||||
"dialog": {
|
||||
@ -484,6 +491,18 @@
|
||||
"button": "Baixar"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Adiciona um equalizador ao player",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Predefinições",
|
||||
"list": {
|
||||
"bass-booster": "Reforço de graves"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Equalizador"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Torna o controle deslizante de volume exponencial para que seja mais fácil selecionar volumes mais baixos.",
|
||||
"name": "Volume Exponencial"
|
||||
@ -714,8 +733,8 @@
|
||||
"synced-lyrics": {
|
||||
"description": "Fornece letras sincronizadas para músicas, usando provedores como LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Ocorreu um erro ao buscar a letra. Tente novamente mais tarde.",
|
||||
"not-found": "⚠️ - Nenhuma letra encontrada para esta música."
|
||||
"fetch": "⚠️\tOcorreu um erro ao buscar a letra.\n\tTente novamente mais tarde.",
|
||||
"not-found": "⚠️ Nenhuma letra encontrada para esta música."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
@ -725,6 +744,10 @@
|
||||
"line-effect": {
|
||||
"label": "Efeito de linha",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "Fancy",
|
||||
"tooltip": "Use efeitos grandes, semelhantes a aplicativos, na linha atual"
|
||||
},
|
||||
"focus": {
|
||||
"label": "Foco",
|
||||
"tooltip": "Deixe apenas a linha atual branca"
|
||||
|
||||
@ -279,6 +279,37 @@
|
||||
},
|
||||
"name": "Modo Ambiente"
|
||||
},
|
||||
"api-server": {
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Permitir",
|
||||
"deny": "Negar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Estratégia de Autorização",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Autorizar ao primeiro pedido"
|
||||
},
|
||||
"none": {
|
||||
"label": "Sem autorização"
|
||||
}
|
||||
}
|
||||
},
|
||||
"port": {
|
||||
"label": "Porta"
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
"port": {
|
||||
"title": "Porta"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Aplicar compressão ao áudio (diminui o volume das partes mais altas do sinal e aumenta o volume das partes mais suaves)",
|
||||
"name": "Compressor de áudio"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Reclamele au sunetul dezactivat si viteza de redare este x16",
|
||||
"name": "Accelerare reclame"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Blocheaza toate reclamele si trackers",
|
||||
"menu": {
|
||||
@ -264,103 +268,153 @@
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"label": "Fluenta tranzitiei",
|
||||
"label": "Fluiditatea tranzitiei",
|
||||
"submenu": {
|
||||
"during": "In timpul {{interpolationTime}} s"
|
||||
}
|
||||
},
|
||||
"use-fullscreen": {
|
||||
"label": "Foloseste fullscreen"
|
||||
"label": "Ecran Plin în utilizare"
|
||||
}
|
||||
},
|
||||
"name": "Mod ambiental"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "Adauga suport Youtube Music pentru Amuse se redă acum widget de 6K Labs",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Server-ul API-ului Amuse rulează. GET /query pentru a obține informații despre melodie."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Adaugă un server API pentru a controla player-ul",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Permite",
|
||||
"deny": "Respinge"
|
||||
},
|
||||
"message": "Permite {{ID}} {{origin}} să acceseze API-ul?",
|
||||
"title": "Cerere autorizare API"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Strategie de autorizare",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Autorizare la prima cerere"
|
||||
},
|
||||
"none": {
|
||||
"label": "Fără autorizare"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Nume host"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port"
|
||||
}
|
||||
},
|
||||
"name": "Server API [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Introduceți nume host (0.0.0.0 de ex.) pentru server-ul API:",
|
||||
"title": "Nume host"
|
||||
},
|
||||
"port": {
|
||||
"label": "Introduceți port-ul pentru server-ul API:",
|
||||
"title": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Aplica compresie pe audio (scade volumul partilor cele mai sonore si creste volumul partilor mai putin sonore)",
|
||||
"description": "Aplică compresie pe audio (scade volumul părților cele mai zgomotoase și crește volumul părților mai puțin zgomotoase)",
|
||||
"name": "Compresor audio"
|
||||
},
|
||||
"blur-nav-bar": {
|
||||
"description": "Fa bara de navigare semi-transparenta",
|
||||
"name": "Bara de naviagtie semi-transparenta"
|
||||
"description": "Face bara de navigare semi-transparentă",
|
||||
"name": "Estompează Bara de Navigație"
|
||||
},
|
||||
"bypass-age-restrictions": {
|
||||
"description": "Treci peste verificarea de varsta a YouTube-ului",
|
||||
"name": "Ignora restrictiile de varsta"
|
||||
"description": "Treci peste verificarea de vârstă a YouTube-ului",
|
||||
"name": "Ignoră restricțiile de vârstă"
|
||||
},
|
||||
"captions-selector": {
|
||||
"description": "Selector de subtitrari pentru piesele audio de pe YouTube Music",
|
||||
"description": "Selector de subtitrări pentru piesele audio de pe YouTube Music",
|
||||
"menu": {
|
||||
"autoload": "Selecteaza automat ultima subtitrare folosita",
|
||||
"disable-captions": "Fara subtitrari by default"
|
||||
"autoload": "Selectează automat ultima subtitrare folosită",
|
||||
"disable-captions": "Fără subtitrări în mod implicit"
|
||||
},
|
||||
"name": "Selector de subtitrari",
|
||||
"name": "Selector de subtitrări",
|
||||
"prompt": {
|
||||
"selector": {
|
||||
"label": "Limba curenta a subtitrarilor: {{language}}",
|
||||
"label": "Limba curentă a subtitrărilor: {{language}}",
|
||||
"none": "Niciuna",
|
||||
"title": "Alege limba subtitrarilor"
|
||||
"title": "Alege limba subtitrărilor"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"title": "Deschide selectorul de subtitrari"
|
||||
"title": "Deschide selectorul de subtitrări"
|
||||
}
|
||||
},
|
||||
"compact-sidebar": {
|
||||
"description": "Pastreaza bara laterala mereu in modul compact",
|
||||
"name": "Bara laterala compacta"
|
||||
"description": "Păstrează bara laterală mereu în modul compact",
|
||||
"name": "Bara Laterală Compactă"
|
||||
},
|
||||
"crossfade": {
|
||||
"description": "Tranzitioneaza intre cantece",
|
||||
"description": "Tranziționează între melodii",
|
||||
"menu": {
|
||||
"advanced": "Avansat"
|
||||
},
|
||||
"name": "Tranzitie [Beta]",
|
||||
"name": "Tranziție [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
"fade-in-duration": "Durata tranzitie de inceput (ms)",
|
||||
"fade-out-duration": "Durata tranzitie de sfarsit (ms)",
|
||||
"fade-in-duration": "Durată tranziție de început (ms)",
|
||||
"fade-out-duration": "Durată tranziției de sfârșit (ms)",
|
||||
"fade-scaling": {
|
||||
"label": "Scala tranzitiei",
|
||||
"linear": "Linear",
|
||||
"label": "Scalare de estompare",
|
||||
"linear": "Liniar",
|
||||
"logarithmic": "Logaritmic"
|
||||
},
|
||||
"seconds-before-end": "Tranzitie N secunde inainte de final"
|
||||
"seconds-before-end": "Tranziție N secunde înainte de final"
|
||||
},
|
||||
"title": "Optiuni de tranzitie"
|
||||
"title": "Opțiuni de tranziție"
|
||||
}
|
||||
}
|
||||
},
|
||||
"disable-autoplay": {
|
||||
"description": "Fa cantecul sa inceapa in modul \"pauza\"",
|
||||
"description": "Face cântecul să înceapă în modul \"pauză\"",
|
||||
"menu": {
|
||||
"apply-once": "Se aplica doar la pornirea aplicatiei"
|
||||
"apply-once": "Se aplică doar la pornirea aplicației"
|
||||
},
|
||||
"name": "Dezactiveaza redarea automata"
|
||||
"name": "Dezactivează redarea automată"
|
||||
},
|
||||
"discord": {
|
||||
"backend": {
|
||||
"already-connected": "S-a incercat conectarea cu o conexiune activa",
|
||||
"already-connected": "S-a încercat conectarea cu o conexiune activă",
|
||||
"connected": "Conectat la Discord",
|
||||
"disconnected": "Deconectat de la Discord"
|
||||
},
|
||||
"description": "Arata-le prietenilor ce asculti cu Rich Presence",
|
||||
"description": "Arată-le prietenilor ce asculți cu Rich Presence",
|
||||
"menu": {
|
||||
"auto-reconnect": "Reconectare automata",
|
||||
"clear-activity": "Sterge activitatea",
|
||||
"clear-activity-after-timeout": "Sterge activitatea dupa timeout",
|
||||
"auto-reconnect": "Reconectare automată",
|
||||
"clear-activity": "Șterge activitatea",
|
||||
"clear-activity-after-timeout": "Șterge activitatea după timeout",
|
||||
"connected": "Conectat",
|
||||
"disconnected": "Deconectat",
|
||||
"hide-duration-left": "Ascunde timpul ramas",
|
||||
"hide-duration-left": "Ascunde timpul rămas",
|
||||
"hide-github-button": "Ascunde butonul cu link-ul GitHub",
|
||||
"play-on-youtube-music": "Reda pe YouTube Music",
|
||||
"set-inactivity-timeout": "Seteaza intervalul de inactivitate"
|
||||
"play-on-youtube-music": "Redă pe YouTube Music",
|
||||
"set-inactivity-timeout": "Setează intervalul de inactivitate"
|
||||
},
|
||||
"name": "Discord Rich Presence",
|
||||
"prompt": {
|
||||
"set-inactivity-timeout": {
|
||||
"label": "Introduceti perioada de inactivitate dorita in secunde:",
|
||||
"title": "Seteaza timpul de inactivitate"
|
||||
"label": "Introduceți perioada de inactivitate dorită în secunde:",
|
||||
"title": "Setează timpul de inactivitate"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -371,74 +425,101 @@
|
||||
"buttons": {
|
||||
"ok": "OK"
|
||||
},
|
||||
"message": "Argh! Scuze, descarcarea a esuat…",
|
||||
"title": "Eroare la descarcare!"
|
||||
"message": "Ah! Scuze, descărcarea a eșuat…",
|
||||
"title": "Eroare la descărcare!"
|
||||
},
|
||||
"start-download-playlist": {
|
||||
"buttons": {
|
||||
"ok": "OK"
|
||||
},
|
||||
"detail": "({{playlistSize}} cantece)",
|
||||
"detail": "({{playlistSize}} melodii)",
|
||||
"message": "Se descarca Playlist-ul {{playlistTitle}}",
|
||||
"title": "Descarcarea a inceput"
|
||||
"title": "Descărcarea a început"
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"conversion-progress": "Conversie: {{percent}}%",
|
||||
"converting": "Se converteste…",
|
||||
"done": "Descarcat: {{filePath}}",
|
||||
"download-info": "Se descarca {{artist}} -{{title}} [{{videoId}}",
|
||||
"download-progress": "Se descarca: {{percent}}%",
|
||||
"downloading": "Se descarca…",
|
||||
"downloading-counter": "Se descarca {{current}}/{{total}}…",
|
||||
"downloading-playlist": "Se descarca playlist-ul \"{{playlistTitle}}\" - {{playlistSize}} piese ({{playlistId}})",
|
||||
"error-while-downloading": "Eroare la descarcarea piesei \"{{author}} - {{title}}\":{{error}}",
|
||||
"folder-already-exists": "Folderul {{playlistFolder}} exista deja",
|
||||
"getting-playlist-info": "Se aduna informatiile despre playlist…",
|
||||
"loading": "Se incarca…",
|
||||
"playlist-has-only-one-song": "Playlist-ul are doar un element, acesta va fi descarcat direct",
|
||||
"playlist-id-not-found": "Niciun ID al playlist-ului nu a fost gasit",
|
||||
"playlist-is-empty": "Playlist-ul este gol",
|
||||
"playlist-is-mix-or-private": "Eroare la colectarea informatiilor despre playlist: asigurati-va ca nu este privat sau un playlist \"Mixed for you\"\n\n{{error}}",
|
||||
"preparing-file": "Se pregateste fisierul…",
|
||||
"saving": "Se salveaza…",
|
||||
"trying-to-get-playlist-id": "Se incearca obtinerea ID-ului playlist-ului: {{playlistId}}",
|
||||
"video-id-not-found": "Video-ul nu a fost gasit",
|
||||
"converting": "Se convertește…",
|
||||
"done": "Descărcat: {{filePath}}",
|
||||
"download-info": "Se descarcă {{artist}} -{{title}} [{{videoId}}",
|
||||
"download-progress": "Se descarcă: {{percent}}%",
|
||||
"downloading": "Se descarcă…",
|
||||
"downloading-counter": "Se descarcă {{current}}/{{total}}…",
|
||||
"downloading-playlist": "Se descarcă lista de redare \"{{playlistTitle}}\" - {{playlistSize}} piese ({{playlistId}})",
|
||||
"error-while-downloading": "Eroare la descarcareă piesei \"{{author}} - {{title}}\":{{error}}",
|
||||
"folder-already-exists": "Dosarul {{playlistFolder}} există deja",
|
||||
"getting-playlist-info": "Se adună informațiile despre lista de redare…",
|
||||
"loading": "Se incarcă…",
|
||||
"playlist-has-only-one-song": "Lista de redare are doar un element, acesta va fi descărcat direct",
|
||||
"playlist-id-not-found": "Niciun ID al listei de redare nu a fost gasit",
|
||||
"playlist-is-empty": "Lista de redare este goală",
|
||||
"playlist-is-mix-or-private": "Eroare la colectarea informațiilor despre lista de redare: asigurați-vă că nu este privat sau o listă de redare \"Mixed for you\"\n\n{{error}}",
|
||||
"preparing-file": "Se pregătește fișierul…",
|
||||
"saving": "Se salvează…",
|
||||
"trying-to-get-playlist-id": "Se încearcă obținerea ID-ului listei de redare: {{playlistId}}",
|
||||
"video-id-not-found": "Videoclipul nu a fost găsit",
|
||||
"writing-id3": "Se scriu tag-urile ID3…"
|
||||
}
|
||||
},
|
||||
"description": "Descarca MP3 / sursa audio direct din interfata",
|
||||
"description": "Descarcă MP3 / sursa audio direct din interfață",
|
||||
"menu": {
|
||||
"choose-download-folder": "Alege folderul de descarcari",
|
||||
"download-playlist": "Descarca playlist-ul",
|
||||
"presets": "Setari implicite",
|
||||
"skip-existing": "Treci peste fisierele existente"
|
||||
"choose-download-folder": "Alege folderul de descărcări",
|
||||
"download-finish-settings": {
|
||||
"label": "Descărcare la finalizare",
|
||||
"prompt": {
|
||||
"last-percent": "După x la sută",
|
||||
"last-seconds": "Ultimele x secunde",
|
||||
"title": "Configurează când să se descarce"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "Avansat",
|
||||
"enabled": "Activat",
|
||||
"mode": "Mod timp",
|
||||
"percent": "Procentaj",
|
||||
"seconds": "Secunde"
|
||||
}
|
||||
},
|
||||
"download-playlist": "Descarcă lista de redare",
|
||||
"presets": "Setări implicite",
|
||||
"skip-existing": "Treci peste fișierele existente"
|
||||
},
|
||||
"name": "Downloader",
|
||||
"name": "Descărcător",
|
||||
"renderer": {
|
||||
"can-not-update-progress": "Nu se poate actualiza progresul"
|
||||
},
|
||||
"templates": {
|
||||
"button": "Descarca"
|
||||
"button": "Descarcă"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Adauă un egalizator la player",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Setări implicite",
|
||||
"list": {
|
||||
"bass-booster": "Amplificator de bas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Egalizator"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Fa slider-ul de volum exponential pentru a fi mai usor de selectat volumuri reduse.",
|
||||
"name": "Volum exponential"
|
||||
"description": "Face glisorul de volum exponențial pentru a fi mai ușor de selectat volume reduse.",
|
||||
"name": "Volum exponențial"
|
||||
},
|
||||
"in-app-menu": {
|
||||
"description": "Ofera barelor de meniu un aspect extravagant, intunecat sau de culoarea albumului",
|
||||
"description": "Oferă barelor de meniu un aspect extravagant, întunecat sau de culoarea albumului",
|
||||
"menu": {
|
||||
"hide-dom-window-controls": "Ascunde controalele ferestrei DOM"
|
||||
},
|
||||
"name": "Meniul aplicatiei"
|
||||
"name": "Meniul aplicației"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Adauga asistenta pentru Lumia Stream",
|
||||
"description": "Adaugă asistenta pentru Lumia Stream",
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Adauga versuri pentru majoritatea cantecelor",
|
||||
"description": "Adaugă versuri pentru majoritatea cântecelor",
|
||||
"menu": {
|
||||
"romanized-lyrics": "Versuri romantizate"
|
||||
},
|
||||
@ -448,29 +529,29 @@
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"description": "Impartaseste playlist-ul cu altii. Cand gazda va pune o piesa, toti ceilalti vor auzi acelasi cantec",
|
||||
"description": "Împărtășește lista de redare cu alții. Când gazda va pune o piesă, toți ceilalți vor auzi aceeași melodie",
|
||||
"dialog": {
|
||||
"enter-host": "Introdu ID-ul host-ului"
|
||||
},
|
||||
"internal": {
|
||||
"save": "Salveaza",
|
||||
"save": "Salvează",
|
||||
"track-source": "Sursa piesei",
|
||||
"unknown-user": "Utilizator necunoscut"
|
||||
},
|
||||
"menu": {
|
||||
"click-to-copy-id": "Copiaza ID-ul host-ului",
|
||||
"close": "Inchide Music Together",
|
||||
"connected-users": "Utilizatori conectati",
|
||||
"disconnect": "Deconecteaza Music Together",
|
||||
"click-to-copy-id": "Copiază ID-ul host-ului",
|
||||
"close": "Închide Music Together",
|
||||
"connected-users": "Utilizatori conecțati",
|
||||
"disconnect": "Deconectează Music Together",
|
||||
"empty-user": "Niciun utilizator conectat",
|
||||
"host": "Gazda Music Together",
|
||||
"join": "Alatura-te Music Together",
|
||||
"join": "Alătura-te Music Together",
|
||||
"permission": {
|
||||
"all": "Permite invitatilor sa controleze playlist-ul si player-ul",
|
||||
"host-only": "Doar gazda poate controla playlist-ul si player-ul",
|
||||
"playlist": "Permite invitatilor controlul asupra playlist-ului"
|
||||
"all": "Permite invitaților să controleze lista de redare si player-ul",
|
||||
"host-only": "Doar gazda poate controla lista de redare și player-ul",
|
||||
"playlist": "Permite invitaților controlul asupra listei de redare"
|
||||
},
|
||||
"set-permission": "Schimba controlul permisiunilor",
|
||||
"set-permission": "Schimbă controlul permisiunilor",
|
||||
"status": {
|
||||
"disconnected": "Deconectat",
|
||||
"guest": "Conectat ca invitat",
|
||||
@ -479,63 +560,63 @@
|
||||
},
|
||||
"name": "Music Together [Beta]",
|
||||
"toast": {
|
||||
"add-song-failed": "Adaugarea piesei a esuat",
|
||||
"closed": "Music Together inchis",
|
||||
"add-song-failed": "Adăugarea piesei a eșuat",
|
||||
"closed": "Music Together închis",
|
||||
"disconnected": "Music Together deconectat",
|
||||
"host-failed": "Nu s-a reusit gazduirea Music Together",
|
||||
"id-copied": "ID-ul host-ului a fost copiat in clipboard",
|
||||
"id-copy-failed": "Eroare la copierea ID-ului host-ului in clipboard",
|
||||
"join-failed": "Nu s-a reusit alaturarea la Music Together",
|
||||
"joined": "V-ati alaturat Music Together",
|
||||
"host-failed": "Nu s-a reușit găzduirea Music Together",
|
||||
"id-copied": "ID-ul host-ului a fost copiat în clipboard",
|
||||
"id-copy-failed": "Eroare la copierea ID-ului host-ului în clipboard",
|
||||
"join-failed": "Nu s-a reușit alăturarea la Music Together",
|
||||
"joined": "V-ați alăturat Music Together",
|
||||
"permission-changed": "Permisiunile Music Together s-au schimbat la \"{{permission}}\"",
|
||||
"remove-song-failed": "Eroare la indepartarea cantecului",
|
||||
"user-connected": "{{name}} s-a alaturat la Music Together",
|
||||
"user-disconnected": "{{name}} a parasit Music Together"
|
||||
"remove-song-failed": "Eroare la îndepărtarea melodiei",
|
||||
"user-connected": "{{name}} s-a alăturat la Music Together",
|
||||
"user-disconnected": "{{name}} a părăsit Music Together"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"description": "Sagetile pentru Urmatorul/Anteriorul integrate direct in interfata, ca in browser-ul tau preferat",
|
||||
"name": "Navigatie"
|
||||
"description": "Săgețile pentru Următorul/Anteriorul integrate direct în interfață, ca în browser-ul tău preferat",
|
||||
"name": "Navigație"
|
||||
},
|
||||
"no-google-login": {
|
||||
"description": "Elimina butonul de autentificare Google si link-urile din interfata",
|
||||
"description": "Elimină butonul de autentificare Google și link-urile din interfață",
|
||||
"name": "Nicio autentificare Google"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "Afiseaza o notificare cand incepe sa cante o piesa (notificarile interactive sunt disponibile pe Windows)",
|
||||
"description": "Afișează o notificare când începe să cânte o piesă (notificările interactive sunt disponibile pe Windows)",
|
||||
"menu": {
|
||||
"interactive": "Notificari interactive",
|
||||
"interactive": "Notificări interactive",
|
||||
"interactive-settings": {
|
||||
"label": "Setari interactive",
|
||||
"label": "Setări interactive",
|
||||
"submenu": {
|
||||
"hide-button-text": "Ascunde textul butoanelor",
|
||||
"refresh-on-play-pause": "Reimprospateaza la Reda/Pauza",
|
||||
"tray-controls": "Deschide/Inchide la apasarea icnoitei pentru meniul Tray"
|
||||
"refresh-on-play-pause": "Reîmprospătează la Redă/Pauză",
|
||||
"tray-controls": "Deschide/Închide la apăsarea iconiței pentru meniul Tray"
|
||||
}
|
||||
},
|
||||
"priority": "Prioritatea notificarilor",
|
||||
"toast-style": "Stilul notificarilor",
|
||||
"unpause-notification": "Arata notificarile la pauza"
|
||||
"priority": "Prioritatea notificărilor",
|
||||
"toast-style": "Stilul notificărilor",
|
||||
"unpause-notification": "Arată notificările la pauză"
|
||||
},
|
||||
"name": "Notificari"
|
||||
"name": "Notificări"
|
||||
},
|
||||
"picture-in-picture": {
|
||||
"description": "Permite sa schimbi aplicatie la modul picture-in-picture",
|
||||
"description": "Permite să schimbi aplicația la modul picture-in-picture",
|
||||
"menu": {
|
||||
"always-on-top": "Mereu deasupra",
|
||||
"hotkey": {
|
||||
"label": "Scurtaturi pe tastatura",
|
||||
"label": "Scurtături pe tastatură",
|
||||
"prompt": {
|
||||
"keybind-options": {
|
||||
"hotkey": "Scurtaturi din taste"
|
||||
"hotkey": "Scurtături din taste"
|
||||
},
|
||||
"label": "Scurtaturi din taste pentru picture-in-picture",
|
||||
"title": "Scurtatura Picture-in-picture"
|
||||
"label": "Alege tasta pentru picture-in-picture",
|
||||
"title": "Scurtătura Picture-in-picture"
|
||||
}
|
||||
},
|
||||
"save-window-position": "Salveaza pozitia ferestrei",
|
||||
"save-window-size": "Salveaza marimea ferestrei",
|
||||
"use-native-pip": "Foloseste PiP-ul nativ pentru broswer"
|
||||
"save-window-position": "Salvează poziția ferestrei",
|
||||
"save-window-size": "Salvează mărimea ferestrei",
|
||||
"use-native-pip": "Folosește PiP-ul nativ pentru broswer"
|
||||
},
|
||||
"name": "Picture-in-picture",
|
||||
"templates": {
|
||||
@ -543,31 +624,31 @@
|
||||
}
|
||||
},
|
||||
"playback-speed": {
|
||||
"description": "Asculta rapid, asculta lent! Adauga un slider pentru viteza de redare a cantecului",
|
||||
"description": "Ascultă rapid, ascultă lent! Adaugă un slider pentru viteza de redare a melodiei",
|
||||
"name": "Viteza de redare",
|
||||
"templates": {
|
||||
"button": "Viteza"
|
||||
"button": "Viteză"
|
||||
}
|
||||
},
|
||||
"precise-volume": {
|
||||
"description": "Controleaza volumul precis folosind rotita mouse-ului/scurtaturi din tastatura, cu un HUD personalizat si incremente de volum personalizate",
|
||||
"description": "Controlează volumul precis folosind rotița mouse-ului/scurtăturii din tastatură, cu un HUD personalizat și incremente de volum personalizate",
|
||||
"menu": {
|
||||
"arrows-shortcuts": "Control cu tastele sageti locale",
|
||||
"custom-volume-steps": "Seteaza incrementele de volum",
|
||||
"global-shortcuts": "Scurtaturi de tastatura globale"
|
||||
"arrows-shortcuts": "Control cu tastele-săgeți locale",
|
||||
"custom-volume-steps": "Setează incrementele de volum",
|
||||
"global-shortcuts": "Scurtături de tastatură globale"
|
||||
},
|
||||
"name": "Volum precis",
|
||||
"prompt": {
|
||||
"global-shortcuts": {
|
||||
"keybind-options": {
|
||||
"decrease": "Redu volumul audio",
|
||||
"increase": "Creste volumul audio"
|
||||
"increase": "Crește volumul audio"
|
||||
},
|
||||
"label": "Alege combinatiile de taste globale pentru volumul audio:",
|
||||
"title": "Combinatii globale de taste pentru volum"
|
||||
"label": "Alege combinațiile de taste globale pentru volumul audio:",
|
||||
"title": "Combinații globale de taste pentru volum"
|
||||
},
|
||||
"volume-steps": {
|
||||
"label": "Alege pasii de increment pentru volum audio",
|
||||
"label": "Alege pașii de increment pentru volum audio",
|
||||
"title": "Incremente de volum"
|
||||
}
|
||||
}
|
||||
@ -576,28 +657,28 @@
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"quality-changer": {
|
||||
"detail": "Calitate actuala: {{quality}}",
|
||||
"message": "Alegeti calitatea video:",
|
||||
"title": "Alegeti calitatea video"
|
||||
"detail": "Calitate actuală: {{quality}}",
|
||||
"message": "Alegeți calitatea video:",
|
||||
"title": "Alegeți calitatea video"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Permite schimbarea calitatii video cu un buton prezent peste video",
|
||||
"name": "Modificator de calitate video"
|
||||
"description": "Permite schimbarea calității video cu un buton prezent peste video",
|
||||
"name": "Schimbător de calitate video"
|
||||
},
|
||||
"scrobbler": {
|
||||
"description": "Adauga asistenta pentru scrobbling (etc. last.fm, Listenbrainz)",
|
||||
"description": "Adaugă asistenta pentru scrobbling (etc. last.fm, Listenbrainz)",
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Autentificarea cu Last.fm a esuat\nAscunde acest pop-up pana la urmatoarea repornire.",
|
||||
"title": "Autentificare Esuata"
|
||||
"message": "Autentificarea cu Last.fm a eșuat\nAscunde acest pop-up până la următoarea repornire.",
|
||||
"title": "Autentificare Eșuată"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"lastfm": {
|
||||
"api-settings": "Setari pentru API-ul Last.fm"
|
||||
"api-settings": "Setări pentru API-ul Last.fm"
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": "Introdu token-ul de utilizator ListenBrainz"
|
||||
@ -612,49 +693,105 @@
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "Introdu token-ul tau de utilizator ListenBrainz:",
|
||||
"label": "Introdu token-ul tău de utilizator ListenBrainz:",
|
||||
"title": "Token-ul ListenBrainz"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"description": "Permite setari globale pentru scurtaturi pe tastatura pentru playback (reda/pauza/urmatorul/anteriorul), pentru oprirea media OSD prin suprascriera tastelor media, pentru folosirea combinatiei Ctrl/CMD + F pentru a cauta, pentru asistenta Linux MPRIS pentru taste media si pentru scurtaturi perosnalizate pentru utilizatori avansati",
|
||||
"description": "Permite setari globale pentru scurtături pe tastatură pentru redare (redă/pauză/următorul/anteriorul) și pentru oprirea media OSD prin suprascriera tastelor media, pentru folosirea combinației Ctrl/CMD + F pentru a căuta, pentru pornirea asistenței Linux MPRIS pentru taste media și pentru scurtături personalizate pentru utilizatori avansați",
|
||||
"menu": {
|
||||
"override-media-keys": "Suprascrie tastele media",
|
||||
"set-keybinds": "Seteaza scurtaturile globale pentru cantece"
|
||||
"set-keybinds": "Setează scurtăturile globale pentru melodii"
|
||||
},
|
||||
"name": "Scurtaturi (& MPRIS)",
|
||||
"name": "Scurtături (& MPRIS)",
|
||||
"prompt": {
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "Urmatorul",
|
||||
"play-pause": "Reda / Pauza",
|
||||
"next": "Următorul",
|
||||
"play-pause": "Redă / Pauză",
|
||||
"previous": "Anteriorul"
|
||||
},
|
||||
"label": "Alege combinatia de taste globala pentru controlul cantecelor:",
|
||||
"title": "Scurtaturi pe tastatura globale"
|
||||
"label": "Alege combinația de taste globală pentru controlul melodiilor:",
|
||||
"title": "Scurtături pe tastatură globale"
|
||||
}
|
||||
}
|
||||
},
|
||||
"skip-disliked-songs": {
|
||||
"description": "Sari peste cantecele disliked",
|
||||
"name": "Treci peste cantecele disliked"
|
||||
"description": "Sari peste melodiile neplăcute",
|
||||
"name": "Treci peste melodiile neplăcute"
|
||||
},
|
||||
"skip-silences": {
|
||||
"description": "Treci automat peste sectiunile de liniste din cantece",
|
||||
"name": "Treci peste liniste"
|
||||
"description": "Treci automat peste secțiunile de liniște din melodii",
|
||||
"name": "Treci peste liniște"
|
||||
},
|
||||
"sponsorblock": {
|
||||
"description": "Treci automat peste partile non-muzicale precum intro/outro sau parti din video-ul catecului, cand nu se aude cantecul",
|
||||
"description": "Treci automat peste părțile non-muzicale precum intro/outro sau părți din video-ul melodiei, când nu se aude melodia",
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Furnizează versuri sincronizate melodiilor, folosind furnizori precum LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - A apărut o eroare în timpul încărcării versurilor. \nTe rog încearcă din nou mai târziu.",
|
||||
"not-found": "⚠️ Nu au fost găsite versuri pentru această melodie."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
"label": "Caracter implicit între versuri",
|
||||
"tooltip": "Alege caracterul implicit folosit pentru spațiul dintre versuri"
|
||||
},
|
||||
"line-effect": {
|
||||
"label": "Efect de linie",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"tooltip": "Folosește efecte largi pe linia curentă"
|
||||
},
|
||||
"focus": {
|
||||
"label": "Focalizare",
|
||||
"tooltip": "Doar linia curentă este albă"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Deplasare",
|
||||
"tooltip": "Deplasare la dreapta pentru linia curentă"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Mărime",
|
||||
"tooltip": "Schimbă dimensiunea liniei curente"
|
||||
}
|
||||
},
|
||||
"tooltip": "Alege efectul aplicat liniei curente"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Sincronizează versurile perfect",
|
||||
"tooltip": "Calculează afisarea următoarei linii până la milisecundă (poate afecta performanța)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Afișează versurile chiar dacă sunt inexacte",
|
||||
"tooltip": "Dacă melodia nu este găsită, plugin-ul încearcă din nou cu o căutare diferită.\nRezultatul acestei încercări poate să nu fie exact."
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "Afișează codurile de timp",
|
||||
"tooltip": "Afișează codurile de timp lângă versuri"
|
||||
}
|
||||
},
|
||||
"name": "Versuri Sincronizate",
|
||||
"refetch-btn": {
|
||||
"fetching": "Încărcare...",
|
||||
"normal": "Reîncărcare versuri"
|
||||
},
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️ - Versurile pot fi desincronizate din cauza unei nepotriviri de durație.",
|
||||
"inexact": "⚠️ - Versurile pentru această melodie pot fi inexacte",
|
||||
"instrumental": "⚠️ - Această melodie este instrumentală"
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Controleaza redarea din Bara de Activitati Windows",
|
||||
"description": "Controlează redarea din Bara de Activități Windows",
|
||||
"name": "Control media in Bara de Activitate"
|
||||
},
|
||||
"touchbar": {
|
||||
"description": "Adauga un widget TouchBar pentru utilizatorii macOS",
|
||||
"description": "Adaugă un widget TouchBar pentru utilizatorii macOS",
|
||||
"name": "TouchBar"
|
||||
},
|
||||
"tuna-obs": {
|
||||
@ -662,17 +799,17 @@
|
||||
"name": "Tuna OBS"
|
||||
},
|
||||
"video-toggle": {
|
||||
"description": "Adauga un buton ce schimba intre modurile Video/Cantec. se poate optional elimia complet optiunea video",
|
||||
"description": "Adaugă un buton ce schimbă între modurile Video/Melodie. De asemenea se poate elimina opțional toată fila video",
|
||||
"menu": {
|
||||
"align": {
|
||||
"label": "Aliniere",
|
||||
"submenu": {
|
||||
"left": "Stanga",
|
||||
"left": "Stânga",
|
||||
"middle": "Mijloc",
|
||||
"right": "Dreapta"
|
||||
}
|
||||
},
|
||||
"force-hide": "Forteaza eliminarea tab-ului video",
|
||||
"force-hide": "Forțează eliminarea filei video",
|
||||
"mode": {
|
||||
"label": "Mod",
|
||||
"submenu": {
|
||||
@ -684,15 +821,15 @@
|
||||
},
|
||||
"name": "Comutator video",
|
||||
"templates": {
|
||||
"button": "Cantec"
|
||||
"button": "Melodie"
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "Adauga un visualizer la player",
|
||||
"description": "Adaugă un vizualizator la player",
|
||||
"menu": {
|
||||
"visualizer-type": "Tip de visualizer"
|
||||
"visualizer-type": "Tip de vizualizator"
|
||||
},
|
||||
"name": "Visualizer"
|
||||
"name": "Vizualizator"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,6 +279,56 @@
|
||||
},
|
||||
"name": "Режим Ambient"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "Добавляет поддержку виджета Amuse „сейчас играет“ от 6K Labs",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Сервер Amuse API запущен. GET /query чтобы получить информацию о треке."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Добавляет API сервер для контроля за плеером",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Разрешить",
|
||||
"deny": "Отказать"
|
||||
},
|
||||
"message": "Разрешить {{ID}} ({{origin}}) доступ к API?",
|
||||
"title": "Запрос на авторизацию в API"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Способ авторизации",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Авторизация при первом запросе"
|
||||
},
|
||||
"none": {
|
||||
"label": "Без авторизации"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Имя хоста"
|
||||
},
|
||||
"port": {
|
||||
"label": "Порт"
|
||||
}
|
||||
},
|
||||
"name": "API Сервер [БЕТА]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Введите имя хоста (на подобии 0.0.0.0) для API сервера:",
|
||||
"title": "Имя хоста"
|
||||
},
|
||||
"port": {
|
||||
"label": "Введите порт для API сервера:",
|
||||
"title": "Порт"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Применяет компрессию к аудио (уменьшает громкость самых громких частей сигнала и повышает громкость самых тихих частей)",
|
||||
"name": "Нормализация аудио"
|
||||
@ -414,6 +464,21 @@
|
||||
"description": "Скачивать MP3 / исходное аудио напрямую из интерфейса",
|
||||
"menu": {
|
||||
"choose-download-folder": "Выберите папку для загрузок",
|
||||
"download-finish-settings": {
|
||||
"label": "Скачать по завершению",
|
||||
"prompt": {
|
||||
"last-percent": "После х процентов",
|
||||
"last-seconds": "Осталось x сек",
|
||||
"title": "Условия скачивания"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "Расширенные настройки",
|
||||
"enabled": "Включено",
|
||||
"mode": "Режим по времени",
|
||||
"percent": "Процент",
|
||||
"seconds": "Секунды"
|
||||
}
|
||||
},
|
||||
"download-playlist": "Скачать плейлист",
|
||||
"presets": "Пресеты",
|
||||
"skip-existing": "Пропускать уже существующие файлы"
|
||||
@ -426,6 +491,18 @@
|
||||
"button": "Скачать"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Добавляет эквалайзер к плееру",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Предустановки",
|
||||
"list": {
|
||||
"bass-booster": "Усилитель баса"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Эквалайзер"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Делает слайдер громкости расширенным чтобы было легче понижать громкость.",
|
||||
"name": "Расширенная громкость"
|
||||
@ -594,7 +671,7 @@
|
||||
"dialog": {
|
||||
"lastfm": {
|
||||
"auth-failed": {
|
||||
"message": "Не удалось войти с помощью Last.fm\nСкрыть сообщение до следующего запуска",
|
||||
"message": "Не удалось войти с помощью Last.fm\nСкрыть сообщение до следующего запуска.",
|
||||
"title": "Ошибка аунтефикации"
|
||||
}
|
||||
}
|
||||
@ -656,8 +733,8 @@
|
||||
"synced-lyrics": {
|
||||
"description": "Предоставляет синхронизированные слова для песен из таких источников, как LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Возникла ошибка во время получения слов. Повторите попытку позже.",
|
||||
"not-found": "⚠️ - Для этой песни не найдено слов."
|
||||
"fetch": "⚠️\tПроизошла ошибка во время получения слов.\n\tПовторите попытку позже.",
|
||||
"not-found": "⚠️ Для этой песни не найдено слов."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
@ -667,6 +744,10 @@
|
||||
"line-effect": {
|
||||
"label": "Эффект строки",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "Красивый",
|
||||
"tooltip": "Использовать большие эффекты строки, как в приложении"
|
||||
},
|
||||
"focus": {
|
||||
"label": "Фокусировка",
|
||||
"tooltip": "Делает только текущую строку белой"
|
||||
@ -695,6 +776,7 @@
|
||||
"tooltip": "Показывает временные метки рядом со словами"
|
||||
}
|
||||
},
|
||||
"name": "Синхронизированные тексты песен",
|
||||
"refetch-btn": {
|
||||
"fetching": "Сбор данных...",
|
||||
"normal": "Обновить слова"
|
||||
|
||||
@ -31,11 +31,74 @@
|
||||
},
|
||||
"theme": {
|
||||
"css-file-not-found": "සීඑස්එස් ගොනුව \"{{cssFile}}\" නොපවතී, නොසලකා හැරීම"
|
||||
},
|
||||
"unresponsive": {
|
||||
"details": "ප්රතිචාර නොදක්වන දෝෂයක් {{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "යෙදුම් කෑශ් නිදහස් කරමින්"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "මෙනුව සැගවී ඇත, 'Alt' යතුර නැවත පෙන්වීමට භාවිතා කරන්න. (හෝ In-App මෙනුවේ 'Escape')",
|
||||
"message": "මෙනුව සැගවීම සාර්තකයි",
|
||||
"title": "මෙනුව සැගවීම සක්රීයයි"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "පසුව",
|
||||
"restart-now": "යෙදුම වසා නැවත ආරම්භ කරන්න"
|
||||
},
|
||||
"detail": "\"{{pluginName}}\" ප්ලගිනය යෙදුම නැවත ආරම්භ කිරීමක් ඉල්ලයි",
|
||||
"message": "\"{{pluginName}}\" නැවත ආරම්භ කළ යුතුය",
|
||||
"title": "නැවත ආරම්භ කිරීම අවශ්යයි"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "ඉවත් වන්න",
|
||||
"relaunch": "නැවත ආරම්භ කරන්න",
|
||||
"wait": "රැදී සිටින්න"
|
||||
},
|
||||
"detail": "සිදු වූ දේ සම්බන්ධව අපගේ කණගාටුව! කළ යුතු දේ තෝරන්න:",
|
||||
"message": "යෙදුම ප්රතිචාර නොදක්වයි",
|
||||
"title": "වින්ඩෝව ප්රතිචාර නොදක්වයි"
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
"disable": "යාවත්කාලීන කිරීම් නවතන්න",
|
||||
"download": "බාගත කිරීම",
|
||||
"ok": "හරි"
|
||||
},
|
||||
"detail": "නව අනුවාදයක් ඇති අතර එය මෙයින් බාගන්න {{downloadLink}}",
|
||||
"message": "නව අනුවාදයක් ඇත",
|
||||
"title": "යාවත්කාලීන කිරීමක් ඇත"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"about": "පිළිබදව",
|
||||
"navigation": {
|
||||
"label": "සංචලනය",
|
||||
"submenu": {
|
||||
"copy-current-url": "යොමුව පිටපත් කරගන්න",
|
||||
"go-back": "පිටුපසට",
|
||||
"go-forward": "ඉදිරියට",
|
||||
"quit": "පිටවන්න",
|
||||
"restart": "යෙදුම යලි අරඹන්න"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"label": "විකල්ප",
|
||||
"submenu": {
|
||||
"advanced-options": {
|
||||
"label": "උසස් විකල්ප",
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "යෙදුම් කෑච් යෙදුම ආරම්භයේදී යලි පිහිටුවන්න",
|
||||
"disable-hardware-acceleration": "දෘඩාංග භාවිත වේගවත් කිරීම් අක්රීය කරන්න",
|
||||
"edit-config-json": "config.json සකසන්න"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "Napaka pri inicilizaciji dodatka {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "Dodatek {{pluginName}}::{{contextName}} izvešen pri {{ms}}ms",
|
||||
"executed-at-ms": "Dodatek {{pluginName}}::{{contextName}} izvršen pri {{ms}}ms",
|
||||
"initialize-failed": "Napaka pri inicilizaciji dodatka \"{{pluginName}}\"",
|
||||
"load-all": "Nalaganje dodatkov",
|
||||
"load-failed": "Napaka pri nalaganju dodatka \"{{pluginName}}\"",
|
||||
@ -36,7 +36,7 @@
|
||||
"details": "Neodzivna napaka!\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "Čiščenje predpolnilnika"
|
||||
"clearing-cache-after-20s": "Čiščenje predpomnilnika"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "Okno se je poskusilo prikazati izven ekrana, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||
@ -46,13 +46,14 @@
|
||||
"hide-menu-enabled": {
|
||||
"detail": "Meni je skrit, pritisni 'Alt' za odpiranje (ali 'Escape' če uporabljaš In-App Meni)",
|
||||
"message": "Skriti meni je prižgan",
|
||||
"title": "Skrij meni uklopljen"
|
||||
"title": "Skrij meni vklopljen"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "Kasneje",
|
||||
"restart-now": "Ponovno zaženi zdaj"
|
||||
},
|
||||
"detail": "\"{{pluginName}}\" dodatek potrebuje ponovni zagon za začetek",
|
||||
"detail": "\"{{pluginName}}\" dodatek potrebuje ponovni zagon",
|
||||
"message": "\"{{pluginName}}\" je potrebno ponovno zagnati",
|
||||
"title": "Potreben je ponovni zagon"
|
||||
},
|
||||
@ -62,7 +63,7 @@
|
||||
"relaunch": "Ponovno zaženi",
|
||||
"wait": "Počakaj"
|
||||
},
|
||||
"detail": "Opravičujemo se za neprijetnost! Prosim odločite se kaj narediti:",
|
||||
"detail": "Opravičujemo se za nevšečnost! Prosim odločite se kaj narediti:",
|
||||
"message": "Aplikacija se ne odziva",
|
||||
"title": "Okno se ne odziva"
|
||||
},
|
||||
@ -82,7 +83,7 @@
|
||||
"navigation": {
|
||||
"label": "Navigacija",
|
||||
"submenu": {
|
||||
"copy-current-url": "Kopiraj trenuten URL",
|
||||
"copy-current-url": "Kopiraj trenutni URL",
|
||||
"go-back": "Nazaj",
|
||||
"go-forward": "Naprej",
|
||||
"quit": "Izhod",
|
||||
@ -95,7 +96,7 @@
|
||||
"advanced-options": {
|
||||
"label": "Dodatne nastavitve",
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "Resetiraj predpolnilnik aplikacije ob zagonu",
|
||||
"auto-reset-app-cache": "Resetiraj predpomnilnik aplikacije ob zagonu",
|
||||
"disable-hardware-acceleration": "Izklopi strojno pospeševanje",
|
||||
"edit-config-json": "Spremeni config.json",
|
||||
"override-user-agent": "Prepiši User-Agent",
|
||||
@ -103,7 +104,7 @@
|
||||
"set-proxy": {
|
||||
"label": "Nastavi proxy",
|
||||
"prompt": {
|
||||
"label": "Napiši naslov Proxy: (pusti prazno, da izklopiš)",
|
||||
"label": "Napiši Proxy naslov: (pusti prazno, da izklopiš)",
|
||||
"placeholder": "Primer: SOCKS5://127.0.0.1:9999",
|
||||
"title": "Nastavi Proxy"
|
||||
}
|
||||
@ -115,7 +116,7 @@
|
||||
"auto-update": "Avtomatsko posodobi",
|
||||
"hide-menu": {
|
||||
"dialog": {
|
||||
"message": "Meni se bo skrit pri naslednjem zagonu, uporabi [Alt] da se prikaže (ali [`] v in-app-menu)",
|
||||
"message": "Meni se bo skrit pri naslednjem zagonu, uporabi [Alt] da se prikaže (ali [`] v meniju aplikacije)",
|
||||
"title": "Skrij meni vklopljen"
|
||||
},
|
||||
"label": "Skrij meni"
|
||||
@ -127,12 +128,12 @@
|
||||
},
|
||||
"label": "Jezik",
|
||||
"submenu": {
|
||||
"to-help-translate": "Želiš pomagati prevediti? Klikni tukaj"
|
||||
"to-help-translate": "Želiš pomagati pri prevajanju? Klikni tukaj"
|
||||
}
|
||||
},
|
||||
"resume-on-start": "Predvajaj zadnjo pesem, ko se aplikacija prižge",
|
||||
"resume-on-start": "Predvajaj zadnjo pesem, ko se aplikacija zažene",
|
||||
"single-instance-lock": "Zaklep ene instance",
|
||||
"start-at-login": "Prižgi pri zagonu",
|
||||
"start-at-login": "Zaženi pri zagonu",
|
||||
"starting-page": {
|
||||
"label": "Začetna stran",
|
||||
"unset": "Ni nastavljeno"
|
||||
@ -153,7 +154,7 @@
|
||||
"default": "Privzeto",
|
||||
"force-show": "Prisilno pokaži",
|
||||
"hide": "Skrij",
|
||||
"label": "Gumb všeč mi je"
|
||||
"label": "Gumbi za všečkanje"
|
||||
},
|
||||
"remove-upgrade-button": "Odstrani gumb za nadgradnjo",
|
||||
"theme": {
|
||||
@ -162,8 +163,8 @@
|
||||
"cancel": "Prekliči",
|
||||
"remove": "Odstrani"
|
||||
},
|
||||
"remove-theme": "Ali želite odstraniti poljubno temo?",
|
||||
"remove-theme-message": "Poljubna tema bo odtranjena"
|
||||
"remove-theme": "Ali želite odstraniti temo po meri?",
|
||||
"remove-theme-message": "Tema po meri bo odstranjena"
|
||||
},
|
||||
"label": "Tema",
|
||||
"submenu": {
|
||||
@ -206,12 +207,16 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "Če se predvaja oglas se zvok utišja. Prav tako se hitrost predvajanja nastavi na 16 krat",
|
||||
"name": "Pospeševanje oglasov"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Izklopi vse oglase od začetka",
|
||||
"description": "Izklopi vse oglase in sledenje",
|
||||
"menu": {
|
||||
"blocker": "Blocker"
|
||||
"blocker": "Blokator"
|
||||
},
|
||||
"name": "Ad Blocker"
|
||||
"name": "Blokator reklam"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Doda Undislike, Dislike, Like, in Unlike gumbe vsem glasbam v seznamu predvajanja ali albumu",
|
||||
@ -221,31 +226,31 @@
|
||||
"description": "Doda dinamično temo in vizualne efekte glede na barve albuma",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Raznerje barv",
|
||||
"label": "Razmerje barv",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Tema Brav Albuma"
|
||||
"name": "Barvna tema Albuma"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "Doda bravn efekt iz video posnetka na ozadje",
|
||||
"description": "Doda barvni učinek iz video posnetka na ozadje",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "količina zameglitve",
|
||||
"label": "Stopnja zameglitve",
|
||||
"submenu": {
|
||||
"pixels": "{{blurAmount}} pikslov"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"label": "Medpolnilnik",
|
||||
"label": "Medpomnilnik",
|
||||
"submenu": {
|
||||
"buffer": "{{buffer}}"
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"label": "Nepreglednost",
|
||||
"label": "Prozornost",
|
||||
"submenu": {
|
||||
"percent": "{{opacity}}%"
|
||||
}
|
||||
@ -269,11 +274,54 @@
|
||||
}
|
||||
},
|
||||
"use-fullscreen": {
|
||||
"label": "Uporablja cel zaslon"
|
||||
"label": "Uporablja celoten zaslon"
|
||||
}
|
||||
},
|
||||
"name": "Ambienten način"
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Doda API strežnik za nadzor predvajalnika",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Dovoli",
|
||||
"deny": "Zavrni"
|
||||
},
|
||||
"message": "Dovolite {{ID}} ({{origin}}) da dostopa do API-ja?",
|
||||
"title": "Prošnja za avtomatizacijo API-ja"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Strategija avtorizacije",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Avtorizacija ob prvem zahtevku"
|
||||
},
|
||||
"none": {
|
||||
"label": "Ni avtorizacije"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port"
|
||||
}
|
||||
},
|
||||
"name": "API strežnik [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Vnesite hostname (npr. 0.0.0.0) za API strežnik:",
|
||||
"title": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"label": "Vnesite port za API strežnik:",
|
||||
"title": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Doda kompresijo zvoka (izenači ravni zvoka, zniža glasnost najglasnejših delov in zviša najtišje)",
|
||||
"name": "Kompresija zvoka"
|
||||
@ -289,7 +337,7 @@
|
||||
"captions-selector": {
|
||||
"description": "Izberi podnapise za YouTube Music zvočne posnetke",
|
||||
"menu": {
|
||||
"autoload": "Avtomatsko uporabi zanje izbrane podnapise",
|
||||
"autoload": "Avtomatsko uporabi zadnje izbrane podnapise",
|
||||
"disable-captions": "Avtomatsko brez podnapisov"
|
||||
},
|
||||
"name": "Izberi podnapise",
|
||||
@ -301,7 +349,7 @@
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"title": "Odpri izbir podnapisov"
|
||||
"title": "Odpri izbiro podnapisov"
|
||||
}
|
||||
},
|
||||
"compact-sidebar": {
|
||||
@ -320,15 +368,186 @@
|
||||
"fade-in-duration": "Čas zbledenja (v pesem) (ms)",
|
||||
"fade-out-duration": "Čas zbledenja (izven pesemi) (ms)",
|
||||
"fade-scaling": {
|
||||
"label": "Fade scaling",
|
||||
"label": "Zbledi skaliranje",
|
||||
"linear": "Linearno",
|
||||
"logarithmic": "Logaritmično"
|
||||
},
|
||||
"seconds-before-end": "Crossfade N seconds before end"
|
||||
"seconds-before-end": "Bledenje (crossfade) N sekund pred koncem"
|
||||
},
|
||||
"title": "Možnosti zbledenja"
|
||||
}
|
||||
}
|
||||
},
|
||||
"disable-autoplay": {
|
||||
"description": "Začne pesem v zaustavljenem načinu",
|
||||
"menu": {
|
||||
"apply-once": "Uporabi samo ob zagonu"
|
||||
},
|
||||
"name": "Onemogoči samodejno predvajanje"
|
||||
},
|
||||
"discord": {
|
||||
"backend": {
|
||||
"already-connected": "Poizkus povezave z aktivno povezavo",
|
||||
"connected": "Povezan na Discord",
|
||||
"disconnected": "Povezava z Discord-om prekinjena"
|
||||
},
|
||||
"description": "Pokaži svojim prijateljem kaj poslušaš z bogato prisotnostjo (Rich Presence)",
|
||||
"menu": {
|
||||
"auto-reconnect": "Samodejna unovična povezava",
|
||||
"clear-activity": "Počisti dejavnost",
|
||||
"clear-activity-after-timeout": "Počisti dejavnost po časovni omejitvi",
|
||||
"connected": "Povezan",
|
||||
"disconnected": "Prekinjena povezava",
|
||||
"hide-duration-left": "Skrij preostali čas",
|
||||
"hide-github-button": "Skrij povezavo do GitHub-a",
|
||||
"play-on-youtube-music": "Predvajaj v YouTube Music",
|
||||
"set-inactivity-timeout": "Nastavite časovno omejitev neaktivnosti"
|
||||
},
|
||||
"name": "Discord bogata prisotnost (Rich Presence)",
|
||||
"prompt": {
|
||||
"set-inactivity-timeout": {
|
||||
"label": "Vnesite časovno omejitev neaktivnosti v sekundah:",
|
||||
"title": "Nastavite časovno omejitev nedejavnosti"
|
||||
}
|
||||
}
|
||||
},
|
||||
"downloader": {
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"error": {
|
||||
"buttons": {
|
||||
"ok": "OK"
|
||||
},
|
||||
"message": "Uff! Se opravičujemo, prenos neuspešen…",
|
||||
"title": "Napaka v prenosu!"
|
||||
},
|
||||
"start-download-playlist": {
|
||||
"buttons": {
|
||||
"ok": "OK"
|
||||
},
|
||||
"detail": "({{playlistSize}} pesmi)",
|
||||
"message": "Prenašanje seznama {{playlistTitle}}",
|
||||
"title": "Prenos se je začel"
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"conversion-progress": "Konverzija: {{percent}}%",
|
||||
"converting": "Pretvarjanje…",
|
||||
"done": "Končano: {{filePath}}",
|
||||
"download-info": "Prenašanje {{artist}} - {{title}} [{{videoId}}",
|
||||
"download-progress": "Prenos: {{percent}}%",
|
||||
"downloading": "Prenašanje…",
|
||||
"downloading-counter": "Prenašanje {{current}}/{{total}}…",
|
||||
"downloading-playlist": "Prenašanje seznama \"{{playlistTitle}}\" - {{playlistSize}} pesmi ({{playlistId}})",
|
||||
"error-while-downloading": "Napaka pri prenosu \"{{author}} - {{title}}\": {{error}}",
|
||||
"folder-already-exists": "Ta mapa {{playlistFolder}} že obstaja",
|
||||
"getting-playlist-info": "Pridobivam informacije o seznamu…",
|
||||
"loading": "Nalaganje…",
|
||||
"playlist-has-only-one-song": "Ta seznam ima samo eno pesem, uporabljam direkten prenos",
|
||||
"playlist-id-not-found": "ID seznama ni najden",
|
||||
"playlist-is-empty": "Seznam je prazen",
|
||||
"playlist-is-mix-or-private": "Napaka v pridobivanju informacij o seznamu: poskrbite da seznam ni zaseben ali \"Mixed for you\" seznam\n\n{{error}}",
|
||||
"preparing-file": "Pripravljanje datoteke…",
|
||||
"saving": "Shranjujem…",
|
||||
"trying-to-get-playlist-id": "Poizkušam pridobiti ID seznama: {{playlistId}}",
|
||||
"video-id-not-found": "Videoposnetek ni najden",
|
||||
"writing-id3": "Zapisujem ID3 oznake…"
|
||||
}
|
||||
},
|
||||
"description": "Prenese MP3 / izviren zvok direktno iz vmesnika",
|
||||
"menu": {
|
||||
"choose-download-folder": "Izberite mapo s prenosi",
|
||||
"download-finish-settings": {
|
||||
"label": "Prenesi ob koncu",
|
||||
"prompt": {
|
||||
"last-percent": "Po x odstotkov",
|
||||
"last-seconds": "Zadnjih x sekund",
|
||||
"title": "Nastavite čas prenosa"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "Napredno",
|
||||
"enabled": "Omogočen",
|
||||
"mode": "Časovni način",
|
||||
"percent": "Odstotek",
|
||||
"seconds": "Sekunde"
|
||||
}
|
||||
},
|
||||
"download-playlist": "Prenesi seznam",
|
||||
"presets": "Prednastavitve",
|
||||
"skip-existing": "Preskoči obstoječe datoteke"
|
||||
},
|
||||
"name": "Prenaševalec",
|
||||
"renderer": {
|
||||
"can-not-update-progress": "Nemorem osvežiti napredka"
|
||||
},
|
||||
"templates": {
|
||||
"button": "Prenos"
|
||||
}
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Drsnik za glasnost naredi eksponenten, da bo lažje izbrati nižje glasnosti.",
|
||||
"name": "Eksponentna glasnost"
|
||||
},
|
||||
"in-app-menu": {
|
||||
"description": "Menijem doda eleganten videz v temnih barvah ali barvah albuma",
|
||||
"menu": {
|
||||
"hide-dom-window-controls": "Skrije DOM gumbe za okno"
|
||||
},
|
||||
"name": "Meni v aplikaciji"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Doda podporo za Lumia pretočno predvajanje",
|
||||
"name": "Lumina pretočno predavanje [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Doda podporo besedil za večino pesmi",
|
||||
"menu": {
|
||||
"romanized-lyrics": "Romanizerana besedila"
|
||||
},
|
||||
"name": "Besedila Genius",
|
||||
"renderer": {
|
||||
"fetched-lyrics": "Prestregel besedila za Genius"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"description": "Delite seznam predvajanja z drugimi. Ko gostitelj predvaja skladbo, bodo vsi ostali slišali isto skladbo",
|
||||
"dialog": {
|
||||
"enter-host": "Vnesite Host ID"
|
||||
},
|
||||
"internal": {
|
||||
"save": "Shrani",
|
||||
"track-source": "Vir pesmi",
|
||||
"unknown-user": "Neznan uporabnik"
|
||||
},
|
||||
"menu": {
|
||||
"click-to-copy-id": "Kopiraj Host ID",
|
||||
"close": "Zapri Glasba Skupaj",
|
||||
"connected-users": "Povezani uporabniki",
|
||||
"disconnect": "Prekini povezavo z Pesmi Skupaj",
|
||||
"empty-user": "Ni povezanih uporabnikov",
|
||||
"host": "Gkasba Skupaj gostitelj",
|
||||
"join": "Pridruži se Glasba Skupaj",
|
||||
"permission": {
|
||||
"all": "Dovoli da gosti nadzorujejo seznam predvajanja in predvajalnik",
|
||||
"host-only": "Samo gostitelj lahko spreminja seznam predvajanja in predvajalnik",
|
||||
"playlist": "Dovoli da gostje nadzorujejo predvajalnik"
|
||||
},
|
||||
"set-permission": "Spremeni dovoljenje nadzora",
|
||||
"status": {
|
||||
"disconnected": "Odklopljen",
|
||||
"guest": "Povezan kot Gost",
|
||||
"host": "Povezan kot Gostitelj"
|
||||
}
|
||||
},
|
||||
"name": "Gkasba Skupaj [Beta]",
|
||||
"toast": {
|
||||
"add-song-failed": "Skladba ni bila dodana",
|
||||
"closed": "Glasba Skupaj zaprto",
|
||||
"disconnected": "Gkasba Skupaj odklopljena",
|
||||
"host-failed": "Gkasba Skupaj nisem mogel gostiti",
|
||||
"id-copied": "Host ID je kopiran v odložišče",
|
||||
"id-copy-failed": "Host ID ni bilo mogoče kopirati"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "หากมีการเล่นโฆษณา เสียงจะถูกปิดและตั้งค่าความเร็วในการเล่นเป็น 16x",
|
||||
"name": "เพิ่มความเร็วโฆษณา"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "บล็อกโฆษณาและการติดตามทั้งหมดอย่างอัตโนมัติ",
|
||||
"menu": {
|
||||
@ -275,6 +279,18 @@
|
||||
},
|
||||
"name": "โหมดสภาพแวดล้อม"
|
||||
},
|
||||
"api-server": {
|
||||
"description": "เพิ่มเซิร์ฟเวอร์ API เพื่อควบคุมการเล่น",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "อนุญาต",
|
||||
"deny": "ปฏิเสธ"
|
||||
},
|
||||
"message": "อนุญาตให้ {{ID}} ({{origin}}) เข้าถึง API หรือไม่?"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "ใช้การบีบอัดเสียง (ลดระดับเสียงของส่วนที่ดังที่สุดของสัญญาณและเพิ่มระดับเสียงของส่วนที่เบาที่สุด)",
|
||||
"name": "เครื่องมือบีบอัดเสียง"
|
||||
|
||||
@ -279,6 +279,56 @@
|
||||
},
|
||||
"name": "Ambiyans Modu"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "6K Labs'ın Amuse oynatma widget'ı için YouTube Music desteği ekler",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Amuse API sunucusu çalışıyor. Şarkı bilgilerini almak için GET /query kullanabilirsiniz."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "APİ ekle ve oynatıcıyı kontrol et",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "İzin ver",
|
||||
"deny": "Reddet"
|
||||
},
|
||||
"message": "{{ID}} ({{origin}}) 'nın APIye erişmesine izin verilsin mi?",
|
||||
"title": "APİ yetkilendirme isteği"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Yetkilendirme stratejisi",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "İlk istekte yetkilendir"
|
||||
},
|
||||
"none": {
|
||||
"label": "Yetkilendirme Yok"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Ana bilgisayar adı"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port"
|
||||
}
|
||||
},
|
||||
"name": "API sunucusu [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "API sunucusu için hostname (örneğin 0.0.0.0) girin:",
|
||||
"title": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"label": "API sunucusu için port girin:",
|
||||
"title": "Bağlantı Noktası"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Ses sıkıştırma (dalganın en gürültülü bölümlerinin ses düzeyini azaltır ve daha yumuşak bölümlerin ses düzeyini artırır)",
|
||||
"name": "Ses Sıkıştırma"
|
||||
@ -441,6 +491,18 @@
|
||||
"button": "İndir"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Oynatıcıya ekolayzer desteği ekler",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Ön Ayarlar",
|
||||
"list": {
|
||||
"bass-booster": "Bass güçlendirici"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Ekolayzer"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Ses seviyesi kaydırıcısını üstel hale getirir, böylece daha düşük ses seviyelerini seçmek daha kolay olur.",
|
||||
"name": "Üstel Ses Seviyesi"
|
||||
@ -671,8 +733,8 @@
|
||||
"synced-lyrics": {
|
||||
"description": "LRClib gibi sağlayıcıları kullanarak şarkılara senkronize şarkı sözleri sağlar.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Şarkı sözleri getirilirken bir hata oluştu. Lütfen daha sonra tekrar deneyin.",
|
||||
"not-found": "⚠️ - Bu şarkı için şarkı sözü bulunamadı."
|
||||
"fetch": "⚠️ \tŞarkı sözleri alınırken bir hata oluştu.\n\tLütfen daha sonra tekrar deneyin.",
|
||||
"not-found": "⚠️ Bu şarkı için şarkı sözleri bulunamadı."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
@ -682,6 +744,10 @@
|
||||
"line-effect": {
|
||||
"label": "Çizgi etkisi",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "Süslü",
|
||||
"tooltip": "Mevcut satırda büyük, uygulama benzeri efektler kullan"
|
||||
},
|
||||
"focus": {
|
||||
"label": "odak",
|
||||
"tooltip": "Yalnızca geçerli satırı beyaz yapın"
|
||||
|
||||
@ -207,6 +207,10 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"ad-speedup": {
|
||||
"description": "При програванні реклами звук вимикається і встановлюється швидкість відтворення 16х",
|
||||
"name": "Пришвидшення релками"
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "Блокувати всю рекламу та відстеження з коробки",
|
||||
"menu": {
|
||||
@ -275,6 +279,56 @@
|
||||
},
|
||||
"name": "Режим навколишнього середовища"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "Додає підтримку YouTube Music для віджета Amuse now playing від 6K Labs",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Сервер Amuse API запущено. Запит GET /query для отримання інформації про пісню."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Додає API сервер для контролю плеєра",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Дозволити",
|
||||
"deny": "Відмінити"
|
||||
},
|
||||
"message": "Дозволити {{ID}} ({{origin}}) доступ до API?",
|
||||
"title": "Запит авторизації до API"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Стратегія авторизації",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Авторизувати при першому запиті"
|
||||
},
|
||||
"none": {
|
||||
"label": "Немає авторизації"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Назва серверу"
|
||||
},
|
||||
"port": {
|
||||
"label": "Порт"
|
||||
}
|
||||
},
|
||||
"name": "API сервер [Бета]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Введіть ім'я хоста (наприклад 0.0.0.0) для API серверу:",
|
||||
"title": "Ім'я хоста"
|
||||
},
|
||||
"port": {
|
||||
"label": "Введіть порт API серверу:",
|
||||
"title": "Порт"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Застосувати стиснення аудіо (зменшити гучність найгучніших фрагментів сигналу та збільшити гучність тихих фрагментів)",
|
||||
"name": "Аудіокомпресор"
|
||||
@ -411,13 +465,17 @@
|
||||
"menu": {
|
||||
"choose-download-folder": "Оберіть папку для завантаження",
|
||||
"download-finish-settings": {
|
||||
"label": "Скачати по завершенню",
|
||||
"prompt": {
|
||||
"last-percent": "Після Х відсотків",
|
||||
"last-seconds": "Останні Х секунд"
|
||||
"last-seconds": "Останні Х секунд",
|
||||
"title": "Налаштувати коли завантажувати"
|
||||
},
|
||||
"submenu": {
|
||||
"advanced": "Розширені",
|
||||
"enabled": "Увімкнено",
|
||||
"percent": "Відсотків",
|
||||
"mode": "Режим часу",
|
||||
"percent": "Відсоток",
|
||||
"seconds": "Секунди"
|
||||
}
|
||||
},
|
||||
@ -433,6 +491,18 @@
|
||||
"button": "Завантажити"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "Додає еквалайзер до програвача",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "Пресети",
|
||||
"list": {
|
||||
"bass-booster": "Підсилювач басів"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Еквалайзер"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "Робить регулятор гучності експоненціальним, що полегшує вибір тихих рівнів гучності.",
|
||||
"name": "Експоненціальна гучність"
|
||||
@ -661,9 +731,10 @@
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"synced-lyrics": {
|
||||
"description": "Додає синхронізовані тексти до пісень використовуючи провайдери, такі як LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - При завантаженні тексту сталась помилка. Спробуйте ще раз пізніше.",
|
||||
"not-found": "⚠️ - До цієї пісні текст не знайдено."
|
||||
"fetch": "⚠️ - При завантаженні слів пісні сталась помилка. Спробуйте пізніше.",
|
||||
"not-found": "⚠️ До цієї пісні текст не знайдено."
|
||||
},
|
||||
"menu": {
|
||||
"default-text-string": {
|
||||
@ -673,10 +744,18 @@
|
||||
"line-effect": {
|
||||
"label": "Лінійний ефект",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "Fancy",
|
||||
"tooltip": "Використовуйте великі, додаткоподібні ефекти на поточному рядку"
|
||||
},
|
||||
"focus": {
|
||||
"label": "Зосереджитись",
|
||||
"tooltip": "Зробити білим лише поточний рядок"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Офсет",
|
||||
"tooltip": "Офсет з права від нинішньої лінії"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Масштабувати",
|
||||
"tooltip": "Масштабуваты поточну лінію"
|
||||
@ -685,7 +764,8 @@
|
||||
"tooltip": "Виберіть ефект, який потрібно застосувати до поточної лінії"
|
||||
},
|
||||
"precise-timing": {
|
||||
"label": "Зробити текст пісні ідеально синхронізованим"
|
||||
"label": "Зробити текст пісні ідеально синхронізованим",
|
||||
"tooltip": "Обчисли до мілісекунд відображення наступного рядка (може мати невеликий вплив на продуктивність)"
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "Показувати текст пісні, навіть якщо він неточний",
|
||||
@ -698,7 +778,7 @@
|
||||
},
|
||||
"name": "Синхронізовані тексти",
|
||||
"refetch-btn": {
|
||||
"fetching": "Отримання...",
|
||||
"fetching": "Завантаження...",
|
||||
"normal": "Перезавантажити текст"
|
||||
},
|
||||
"warnings": {
|
||||
|
||||
134
src/i18n/resources/ur.json
Normal file
@ -0,0 +1,134 @@
|
||||
{
|
||||
"common": {
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "پلگ ان {{pluginName}}::{{contextName}} پر عمل کرنے میں ناکام",
|
||||
"executed-at-ms": "پلگ ان {{pluginName}}::{{contextName}} کو {{ms}}ms پر عمل میں لایا گیا",
|
||||
"initialize-failed": "پلگ ان \"{{pluginName}}\" کو شروع کرنے میں ناکام",
|
||||
"load-all": "تمام پلگ ان لوڈ ہو رہے ہیں",
|
||||
"load-failed": "\"{{pluginName}}\" پلگ ان لوڈ کرنے میں ناکام",
|
||||
"loaded": "پلگ ان \"{{pluginName}}\" لوڈ ہو گیا",
|
||||
"unload-failed": "پلگ ان \"{{pluginName}}\" کو لوڈ کرنے میں ناکام",
|
||||
"unloaded": "پلگ ان \"{{pluginName}}\" کو لوڈ نہیں کیا گیا"
|
||||
}
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"code": "ur",
|
||||
"local-name": "اردو",
|
||||
"name": "Urdu"
|
||||
},
|
||||
"main": {
|
||||
"console": {
|
||||
"did-finish-load": {
|
||||
"dev-tools": "لوڈنگ مکمل ہو گئی۔ DevTools کھل گیا"
|
||||
},
|
||||
"i18n": {
|
||||
"loaded": "i18n لوڈ ہو گیا"
|
||||
},
|
||||
"second-instance": {
|
||||
"receive-command": "پروٹوکول پر کمانڈ موصول ہوئی: \"{{command}}\""
|
||||
},
|
||||
"theme": {
|
||||
"css-file-not-found": "CSS فائل \"{{cssFile}}\" موجود نہیں ہے، نظر انداز کر رہے ہیں"
|
||||
},
|
||||
"unresponsive": {
|
||||
"details": "غیر جوابی غلطی!\n{{error}}"
|
||||
},
|
||||
"when-ready": {
|
||||
"clearing-cache-after-20s": "ایپ کیشے کو صاف کرنا"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "ونڈو نے آف اسکرین رینڈر کرنے کی کوشش کی، windowSize={{windowSize}}، displaySize={{displaySize}}، position={{position}}"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"hide-menu-enabled": {
|
||||
"detail": "مینو پوشیدہ ہے، اسے دکھانے کے لیے 'Alt' استعمال کریں (یا 'Escape' اگر ایپ مینیو استعمال کر رہے ہیں)",
|
||||
"message": "پوشیدہ مینو فعال ہے",
|
||||
"title": "پوشیدہ مینو فعال ہو گیا"
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "بعد میں",
|
||||
"restart-now": "ابھی دوبارہ شروع کریں"
|
||||
},
|
||||
"detail": "\"{{pluginName}}\" پلگ ان کو اثر انداز ہونے کے لیے دوبارہ شروع کرنے کی ضرورت ہے",
|
||||
"message": "\"{{pluginName}}\" کو دوبارہ شروع کرنے کی ضرورت ہے",
|
||||
"title": "دوبارہ شروع کرنے کی ضرورت ہے"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "چھوڑو",
|
||||
"relaunch": "دوبارہ لانچ کریں",
|
||||
"wait": "انتظار کرو"
|
||||
},
|
||||
"detail": "ہم زحمت کے لیے معذرت خواہ ہیں! براہ کرم منتخب کریں کہ کیا کرنا ہے:",
|
||||
"message": "پروگرام غیر ذمہ دار ہے",
|
||||
"title": "ونڈو غیر جوابدہ"
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
"disable": "اپ ڈیٹس کو غیر فعال کریں",
|
||||
"download": "ڈاؤن لوڈ کریں",
|
||||
"ok": "ٹھیک ہے"
|
||||
},
|
||||
"detail": "ایک نیا ورژن دستیاب ہے اور اسے {{downloadLink}} پر ڈاؤن لوڈ کیا جا سکتا ہے",
|
||||
"message": "ایک نیا ورژن دستیاب ہے",
|
||||
"title": "اپ ڈیٹ دستیاب ہے"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"about": "پروگرام کے بارے میں",
|
||||
"navigation": {
|
||||
"label": "نیویگیشن",
|
||||
"submenu": {
|
||||
"copy-current-url": "موجودہ URL کاپی کریں",
|
||||
"go-back": "واپس جاؤ",
|
||||
"go-forward": "آگے بڑھو",
|
||||
"quit": "باہر نکلیں",
|
||||
"restart": "ایپ کو دوبارہ شروع کریں"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"label": "آپشنز",
|
||||
"submenu": {
|
||||
"advanced-options": {
|
||||
"label": "اعلی درجے کے آپشنز",
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "ایپ شروع ہونے پر ایپ کیشے کو دوبارہ ترتیب دیں",
|
||||
"disable-hardware-acceleration": "ہارڈ ویئر ایکسلریشن کو غیر فعال کریں",
|
||||
"edit-config-json": "config.json میں ترمیم کریں",
|
||||
"override-user-agent": "یوزر ایجنٹ کو اوور رائیڈ کریں",
|
||||
"restart-on-config-changes": "کنفیگریشن تبدیلیوں پر دوبارہ شروع کریں",
|
||||
"set-proxy": {
|
||||
"label": "پراکسی سیٹ کریں",
|
||||
"prompt": {
|
||||
"label": "پراکسی ایڈریس درج کریں: (غیر فعال کرنے کے لیے خالی چھوڑ دیں)",
|
||||
"placeholder": "مثال: SOCKS5://127.0.0.1:9999",
|
||||
"title": "پراکسی سیٹ کریں"
|
||||
}
|
||||
},
|
||||
"toggle-dev-tools": "DevTools ٹوگل کریں"
|
||||
}
|
||||
},
|
||||
"always-on-top": "ہمیشہ اوپر",
|
||||
"auto-update": "خودکار اپ ڈیٹ",
|
||||
"hide-menu": {
|
||||
"dialog": {
|
||||
"message": "اگلے لانچ پر مینو کو چھپایا جائے گا، اسے دکھانے کے لیے [Alt] استعمال کریں (یا in-app-menu استعمال کرنے پر بیک ٹک [`] کریں)",
|
||||
"title": "پوشیدہ مینو کو فعال کر دیا گیا"
|
||||
},
|
||||
"label": "مینو کو چھپائیں"
|
||||
},
|
||||
"language": {
|
||||
"dialog": {
|
||||
"message": "دوبارہ شروع کرنے کے بعد زبان بدل دی جائے گی",
|
||||
"title": "زبان بدل گئی ہے"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -279,6 +279,55 @@
|
||||
},
|
||||
"name": "Chế độ Môi trường xung quanh"
|
||||
},
|
||||
"amuse": {
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Máy chủ API của Amuse đang chạy. GET /query để lấy thông tin về bài hát."
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Thêm máy chủ API để điều khiển trình phát",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Cho phép",
|
||||
"deny": "Từ chối"
|
||||
},
|
||||
"message": "Cho phép {{ID}} ({{origin}}) truy cập API?",
|
||||
"title": "Yêu cầu cho phép API"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Chiến thuật xác thực",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Xác thực ngay yêu cầu đầu tiên"
|
||||
},
|
||||
"none": {
|
||||
"label": "Không xác thực"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Tên máy chủ"
|
||||
},
|
||||
"port": {
|
||||
"label": "Cổng"
|
||||
}
|
||||
},
|
||||
"name": "Máy chủ API [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Điền tên máy chủ (như 0.0.0.0) cho máy chủ API:",
|
||||
"title": "Tên máy chủ"
|
||||
},
|
||||
"port": {
|
||||
"label": "Nhập cổng cho máy chủ API:",
|
||||
"title": "Cổng"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Áp dụng tính năng nén cho âm thanh (giảm âm lượng của phần to nhất của tín hiệu và tăng âm lượng của phần nhỏ nhất)",
|
||||
"name": "Bộ nén âm thanh"
|
||||
@ -671,7 +720,7 @@
|
||||
"synced-lyrics": {
|
||||
"description": "Cung cấp lời bài hát được đồng bộ hoá với các bài hát, sử dụng những nhà cung cấp như LRClib.",
|
||||
"errors": {
|
||||
"fetch": "⚠️ - Đã xảy ra lỗi khi tìm nạp lời bài hát, Vui lòng thử lại sau.",
|
||||
"fetch": "⚠️ - Đã xảy ra lỗi khi tìm lời bài hát, Vui lòng thử lại sau.",
|
||||
"not-found": "⚠️ - Không tìm thấy lời cho bài hát này."
|
||||
},
|
||||
"menu": {
|
||||
@ -686,6 +735,10 @@
|
||||
"label": "Tập trung",
|
||||
"tooltip": "Chỉ làm cho dòng hiện tại có màu trắng"
|
||||
},
|
||||
"offset": {
|
||||
"label": "Độ lệch",
|
||||
"tooltip": "Độ lệch bên phải của dòng hiện tại"
|
||||
},
|
||||
"scale": {
|
||||
"label": "Tỉ lệ",
|
||||
"tooltip": "Áp dụng tỉ lệ cho dòng hiện tại"
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
"clearing-cache-after-20s": "正在清理应用缓存"
|
||||
},
|
||||
"window": {
|
||||
"tried-to-render-offscreen": "窗口试图于屏幕外绘制, windowSize={{windowSize}}, displaySize={{displaySize}}, position={{position}}"
|
||||
"tried-to-render-offscreen": "窗口试图于屏幕外绘制,窗口大小={{windowSize}},显示尺寸={{displaySize}},位置={{position}}"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
@ -279,6 +279,56 @@
|
||||
},
|
||||
"name": "沉浸模式"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "为 6K Labs 的 Amuse 正在播放小部件添加 YouTube Music 支持",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Amuse API服务器已在运行。使用 /query 以获取歌曲信息。"
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "添加一个 API 服务器来控制播放器",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "允许",
|
||||
"deny": "拒绝"
|
||||
},
|
||||
"message": "允许 {{ID}} {{origin}} 访问该 API 吗?",
|
||||
"title": "API 授权请求"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "授权策略",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "首次请求时授权"
|
||||
},
|
||||
"none": {
|
||||
"label": "无需授权"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "主机名"
|
||||
},
|
||||
"port": {
|
||||
"label": "端口号"
|
||||
}
|
||||
},
|
||||
"name": "API 服务器 [测试]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "请输入 API 服务器的主机名(如 0.0.0.0):",
|
||||
"title": "主机名"
|
||||
},
|
||||
"port": {
|
||||
"label": "请输入 API 服务器的端口号:",
|
||||
"title": "端口号"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "对音频应用压缩(压低响亮部分,提升柔和部分)",
|
||||
"name": "音频压缩器"
|
||||
@ -441,6 +491,18 @@
|
||||
"button": "下载"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "为播放器添加均衡器",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "预设",
|
||||
"list": {
|
||||
"bass-booster": "低音增强器"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "均衡器"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "让音量滑块指数化以便选择更低的音量。",
|
||||
"name": "指数化音量"
|
||||
@ -459,11 +521,11 @@
|
||||
"lyrics-genius": {
|
||||
"description": "为大多数歌曲添加歌词支持",
|
||||
"menu": {
|
||||
"romanized-lyrics": "罗马化字幕"
|
||||
"romanized-lyrics": "罗马化歌词"
|
||||
},
|
||||
"name": "Genius 歌词",
|
||||
"renderer": {
|
||||
"fetched-lyrics": "已从 Genius 获取字幕"
|
||||
"fetched-lyrics": "已从 Genius 获取歌词"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
@ -478,12 +540,12 @@
|
||||
},
|
||||
"menu": {
|
||||
"click-to-copy-id": "复制发起者 ID",
|
||||
"close": "关闭 Music Together",
|
||||
"close": "关闭一起听",
|
||||
"connected-users": "已连接用户",
|
||||
"disconnect": "断开 Music Together 连接",
|
||||
"disconnect": "断开一起听连接",
|
||||
"empty-user": "没有已连接的用户",
|
||||
"host": "Music Together 发起者",
|
||||
"join": "加入 Music Together",
|
||||
"host": "一起听发起者",
|
||||
"join": "加入一起听",
|
||||
"permission": {
|
||||
"all": "允许来宾控制播放列表与播放器",
|
||||
"host-only": "仅发起人可以控制播放列表与播放器",
|
||||
@ -496,20 +558,20 @@
|
||||
"host": "已作为发起人连接"
|
||||
}
|
||||
},
|
||||
"name": "Music Together [测试]",
|
||||
"name": "一起听 [测试]",
|
||||
"toast": {
|
||||
"add-song-failed": "添加歌曲失败",
|
||||
"closed": "Music Together 已关闭",
|
||||
"disconnected": "Music Together 已断开连接",
|
||||
"host-failed": "发起 Music Together 失败",
|
||||
"closed": "一起听已关闭",
|
||||
"disconnected": "一起听已断开连接",
|
||||
"host-failed": "发起一起听失败",
|
||||
"id-copied": "已将发起者 ID 复制到剪切板",
|
||||
"id-copy-failed": "复制发起者 ID 到剪贴板时失败",
|
||||
"join-failed": "加入 Music Together 失败",
|
||||
"joined": "已加入 Music Together",
|
||||
"permission-changed": "Music Together 权限已改为 \"{{permission}}\"",
|
||||
"join-failed": "加入一起听失败",
|
||||
"joined": "已加入一起听",
|
||||
"permission-changed": "一起听权限已改为 \"{{permission}}\"",
|
||||
"remove-song-failed": "移除歌曲失败",
|
||||
"user-connected": "{{name}} 加入了 Music Together",
|
||||
"user-disconnected": "{{name}} 离开了 Music Together"
|
||||
"user-connected": "{{name}} 加入了一起听",
|
||||
"user-disconnected": "{{name}} 离开了一起听"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
@ -631,7 +693,7 @@
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "输入您的v ListenBrainz 用户令牌:",
|
||||
"label": "输入您的 ListenBrainz 用户令牌:",
|
||||
"title": "ListenBrainz 令牌"
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,6 +279,56 @@
|
||||
},
|
||||
"name": "微光效果"
|
||||
},
|
||||
"amuse": {
|
||||
"description": "加入支援 6K Labs 的 Amuse OBS 外掛以取得 Youtube Music 現正播放資訊",
|
||||
"name": "Amuse",
|
||||
"response": {
|
||||
"query": "Amuse API 伺服器正在運行中,使用 /query 以取得歌曲資訊。"
|
||||
}
|
||||
},
|
||||
"api-server": {
|
||||
"description": "新增伺服器以使用 API 控制播放器",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "允許",
|
||||
"deny": "拒絕"
|
||||
},
|
||||
"message": "允許 {{ID}} ({{origin}}) 訪問 API 嗎?",
|
||||
"title": "API 驗證請求"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "驗證策略",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "首次請求時驗證"
|
||||
},
|
||||
"none": {
|
||||
"label": "不要驗證"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "主機名稱"
|
||||
},
|
||||
"port": {
|
||||
"label": "接口"
|
||||
}
|
||||
},
|
||||
"name": "API 伺服器 [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "輸入 API 伺服器的主機名稱 例 (0.0.0.0):",
|
||||
"title": "主機名稱"
|
||||
},
|
||||
"port": {
|
||||
"label": "輸入 API 伺服器接口:",
|
||||
"title": "接口"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "使用音效壓縮 (大聲部份的音量降低, 柔和部份的音量提高)",
|
||||
"name": "音效壓縮器"
|
||||
@ -300,7 +350,7 @@
|
||||
"name": "標題選擇器",
|
||||
"prompt": {
|
||||
"selector": {
|
||||
"label": "目前標題語言: {{language}}",
|
||||
"label": "目前標題語言:{{language}}",
|
||||
"none": "無",
|
||||
"title": "選擇標題語言"
|
||||
}
|
||||
@ -363,7 +413,7 @@
|
||||
"name": "Discord 狀態",
|
||||
"prompt": {
|
||||
"set-inactivity-timeout": {
|
||||
"label": "設定多少秒後清除狀態:",
|
||||
"label": "設定多少秒後清除狀態:",
|
||||
"title": "設定閒置狀態時長"
|
||||
}
|
||||
}
|
||||
@ -388,11 +438,11 @@
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"conversion-progress": "轉檔進度: {{percent}}%",
|
||||
"conversion-progress": "轉檔進度:{{percent}}%",
|
||||
"converting": "轉檔中…",
|
||||
"done": "完成下載: {{filePath}}",
|
||||
"done": "完成下載:{{filePath}}",
|
||||
"download-info": "正在下載 {{artist}} - {{title}} [{{videoId}}",
|
||||
"download-progress": "下載進度: {{percent}}%",
|
||||
"download-progress": "下載進度:{{percent}}%",
|
||||
"downloading": "下載中…",
|
||||
"downloading-counter": "正在下載第 {{current}}/{{total}}…",
|
||||
"downloading-playlist": "正在下載播放清單 \"{{playlistTitle}}\" - 共 {{playlistSize}} 首歌 ({{playlistId}})",
|
||||
@ -403,10 +453,10 @@
|
||||
"playlist-has-only-one-song": "播放清單內只有一首歌曲, 將直接下載",
|
||||
"playlist-id-not-found": "沒有找到播放清單 ID",
|
||||
"playlist-is-empty": "播放清單是空的",
|
||||
"playlist-is-mix-or-private": "獲取播放清單資訊時發生錯誤: 請確認非私人播放清單或是\"為你推薦的合輯\"\n\n{{error}}",
|
||||
"playlist-is-mix-or-private": "獲取播放清單資訊時發生錯誤:請確認非私人播放清單或是\"為你推薦的合輯\"\n\n{{error}}",
|
||||
"preparing-file": "正在準備檔案…",
|
||||
"saving": "儲存中…",
|
||||
"trying-to-get-playlist-id": "正在嘗試獲取播放清單 ID: {{playlistId}}",
|
||||
"trying-to-get-playlist-id": "正在嘗試獲取播放清單 ID:{{playlistId}}",
|
||||
"video-id-not-found": "未能找到該影片",
|
||||
"writing-id3": "正在寫入 ID3 標籤…"
|
||||
}
|
||||
@ -441,6 +491,18 @@
|
||||
"button": "下載"
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"description": "為播放器加入等化器",
|
||||
"menu": {
|
||||
"presets": {
|
||||
"label": "預設格式",
|
||||
"list": {
|
||||
"bass-booster": "低音增強器"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "等化器"
|
||||
},
|
||||
"exponential-volume": {
|
||||
"description": "使音量滑桿指數化,以便更容易選擇較低的音量。",
|
||||
"name": "指數化音量調整"
|
||||
@ -463,7 +525,7 @@
|
||||
},
|
||||
"name": "第三方字幕",
|
||||
"renderer": {
|
||||
"fetched-lyrics": "為Genius獲取字幕"
|
||||
"fetched-lyrics": "為 Genius 獲取字幕"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
@ -582,7 +644,7 @@
|
||||
"decrease": "降低音量",
|
||||
"increase": "增加音量"
|
||||
},
|
||||
"label": "選擇全域音量控制快捷鍵:",
|
||||
"label": "選擇全域音量控制快捷鍵:",
|
||||
"title": "全域音量控制快捷鍵"
|
||||
},
|
||||
"volume-steps": {
|
||||
@ -595,8 +657,8 @@
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"quality-changer": {
|
||||
"detail": "目前畫質: {{quality}}",
|
||||
"message": "選擇影片畫質:",
|
||||
"detail": "目前畫質:{{quality}}",
|
||||
"message": "選擇影片畫質:",
|
||||
"title": "選擇影片畫質"
|
||||
}
|
||||
}
|
||||
@ -651,7 +713,7 @@
|
||||
"play-pause": "播放/暫停",
|
||||
"previous": "上一首"
|
||||
},
|
||||
"label": "選擇全域音樂控制快捷鍵:",
|
||||
"label": "選擇全域音樂控制快捷鍵:",
|
||||
"title": "全域快捷鍵"
|
||||
}
|
||||
}
|
||||
@ -671,7 +733,7 @@
|
||||
"synced-lyrics": {
|
||||
"description": "使用 LRClib 等管道提供歌詞同步顯示。",
|
||||
"errors": {
|
||||
"fetch": "⚠️擷取歌詞時發生錯誤。請稍後再試。",
|
||||
"fetch": "⚠️\t擷取歌詞時發生錯誤\n請稍後再試。",
|
||||
"not-found": "⚠️未找到該首歌曲的歌詞。"
|
||||
},
|
||||
"menu": {
|
||||
@ -682,6 +744,10 @@
|
||||
"line-effect": {
|
||||
"label": "歌詞顯示效果",
|
||||
"submenu": {
|
||||
"fancy": {
|
||||
"label": "絢麗",
|
||||
"tooltip": "使用較為接近原生樣式並且放大當前該行歌詞"
|
||||
},
|
||||
"focus": {
|
||||
"label": "高亮",
|
||||
"tooltip": "高亮當前的歌詞"
|
||||
@ -703,7 +769,7 @@
|
||||
},
|
||||
"show-lyrics-even-if-inexact": {
|
||||
"label": "即使不精確依然強制顯示歌詞",
|
||||
"tooltip": "當找不到符合該歌曲的歌詞時,該功能會嘗試不同的搜尋方式\n使用不同的搜尋方式會導致不精確的結果。"
|
||||
"tooltip": "當找不到符合該歌曲的歌詞時,該功能會嘗試不同的搜尋方式。\n使用不同的搜尋方式會導致不精確的結果。"
|
||||
},
|
||||
"show-time-codes": {
|
||||
"label": "顯示時間線",
|
||||
@ -718,7 +784,7 @@
|
||||
"warnings": {
|
||||
"duration-mismatch": "⚠️歌詞可能會出現不同步的情況。",
|
||||
"inexact": "⚠️該歌曲的歌詞可能並不精確",
|
||||
"instrumental": "⚠️該首歌曲並無人聲"
|
||||
"instrumental": "⚠️該首歌曲為純音樂"
|
||||
}
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
|
||||
35
src/index.ts
@ -131,17 +131,13 @@ if (config.get('options.disableHardwareAcceleration')) {
|
||||
}
|
||||
|
||||
if (is.linux()) {
|
||||
const disabledFeatures = [
|
||||
// Workaround for issue #2248
|
||||
'UseMultiPlaneFormatForSoftwareVideo',
|
||||
];
|
||||
// Overrides WM_CLASS for X11 to correspond to icon filename
|
||||
app.setName('com.github.th_ch.youtube_music');
|
||||
|
||||
// Stops chromium from launching its own MPRIS service
|
||||
if (config.plugins.isEnabled('shortcuts')) {
|
||||
disabledFeatures.push('MediaSessionService');
|
||||
app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
|
||||
}
|
||||
|
||||
app.commandLine.appendSwitch('disable-features', disabledFeatures.join());
|
||||
}
|
||||
|
||||
if (config.get('options.proxy')) {
|
||||
@ -501,10 +497,11 @@ app.once('browser-window-created', (_event, win) => {
|
||||
// User agents are from https://developers.whatismybrowser.com/useragents/explore/
|
||||
const originalUserAgent = win.webContents.userAgent;
|
||||
const userAgents = {
|
||||
mac: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12.1; rv:95.0) Gecko/20100101 Firefox/95.0',
|
||||
mac: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.152 Safari/537.36',
|
||||
windows:
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0',
|
||||
linux: 'Mozilla/5.0 (Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.152 Safari/537.36',
|
||||
linux:
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.152 Safari/537.36',
|
||||
};
|
||||
|
||||
const updatedUserAgent = is.macOS()
|
||||
@ -900,9 +897,21 @@ function removeContentSecurityPolicy(
|
||||
betterSession.webRequest.onHeadersReceived((details, callback) => {
|
||||
details.responseHeaders ??= {};
|
||||
|
||||
// Remove the content security policy
|
||||
delete details.responseHeaders['content-security-policy-report-only'];
|
||||
delete details.responseHeaders['content-security-policy'];
|
||||
// prettier-ignore
|
||||
if (new URL(details.url).protocol === 'https:') {
|
||||
// Remove the content security policy
|
||||
delete details.responseHeaders['content-security-policy-report-only'];
|
||||
delete details.responseHeaders['Content-Security-Policy-Report-Only'];
|
||||
delete details.responseHeaders['content-security-policy'];
|
||||
delete details.responseHeaders['Content-Security-Policy'];
|
||||
|
||||
if (
|
||||
!details.responseHeaders['access-control-allow-origin'] &&
|
||||
!details.responseHeaders['Access-Control-Allow-Origin']
|
||||
) {
|
||||
details.responseHeaders['access-control-allow-origin'] = ['https://music.youtube.com'];
|
||||
}
|
||||
}
|
||||
|
||||
callback({ cancel: false, responseHeaders: details.responseHeaders });
|
||||
});
|
||||
|
||||
10
src/menu.ts
@ -68,7 +68,7 @@ export const mainMenuTemplate = async (
|
||||
win: BrowserWindow,
|
||||
): Promise<MenuTemplate> => {
|
||||
const innerRefreshMenu = () => refreshMenu(win);
|
||||
|
||||
const { navigationHistory } = win.webContents;
|
||||
await loadAllMenuPlugins(win);
|
||||
|
||||
const menuResult = Object.entries(getAllMenuTemplate()).map(
|
||||
@ -610,16 +610,16 @@ export const mainMenuTemplate = async (
|
||||
{
|
||||
label: t('main.menu.navigation.submenu.go-back'),
|
||||
click() {
|
||||
if (win.webContents.canGoBack()) {
|
||||
win.webContents.goBack();
|
||||
if (navigationHistory.canGoBack()) {
|
||||
navigationHistory.goBack();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('main.menu.navigation.submenu.go-forward'),
|
||||
click() {
|
||||
if (win.webContents.canGoForward()) {
|
||||
win.webContents.goForward();
|
||||
if (navigationHistory.canGoForward()) {
|
||||
navigationHistory.goForward();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import path from 'node:path';
|
||||
import fs, { promises } from 'node:fs';
|
||||
|
||||
import { ElectronBlocker } from '@cliqz/adblocker-electron';
|
||||
import { ElectronBlocker } from '@ghostery/adblocker-electron';
|
||||
import { app, net } from 'electron';
|
||||
|
||||
const SOURCES = [
|
||||
@ -55,6 +55,7 @@ export const loadAdBlockerEngine = async (
|
||||
(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
|
||||
@ -66,7 +67,7 @@ export const loadAdBlockerEngine = async (
|
||||
blocker.enableBlockingInSession(session);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error loading adBlocker engine', error);
|
||||
console.error('Error loading adBlocker engine', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@ import {
|
||||
unloadAdBlockerEngine,
|
||||
} from './blocker';
|
||||
|
||||
import injectCliqzPreload from './injectors/inject-cliqz-preload';
|
||||
import { inject, isInjected } from './injectors/inject';
|
||||
import { loadAdSpeedup } from './adSpeedup';
|
||||
|
||||
@ -134,18 +133,13 @@ export default createPlugin({
|
||||
async start({ getConfig }) {
|
||||
const config = await getConfig();
|
||||
|
||||
if (config.blocker === blockers.WithBlocklists) {
|
||||
// Preload adblocker to inject scripts/styles
|
||||
await injectCliqzPreload();
|
||||
} else if (config.blocker === blockers.InPlayer && !isInjected()) {
|
||||
if (config.blocker === blockers.InPlayer && !isInjected()) {
|
||||
inject(contextBridge);
|
||||
await webFrame.executeJavaScript(this.script);
|
||||
}
|
||||
},
|
||||
async onConfigChange(newConfig) {
|
||||
if (newConfig.blocker === blockers.WithBlocklists) {
|
||||
await injectCliqzPreload();
|
||||
} else if (newConfig.blocker === blockers.InPlayer && !isInjected()) {
|
||||
if (newConfig.blocker === blockers.InPlayer && !isInjected()) {
|
||||
inject(contextBridge);
|
||||
await webFrame.executeJavaScript(this.script);
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export default async () => {
|
||||
await import('@cliqz/adblocker-electron-preload');
|
||||
await import('@ghostery/adblocker-electron-preload');
|
||||
};
|
||||
|
||||
@ -133,9 +133,11 @@ export default createPlugin<
|
||||
}
|
||||
},
|
||||
loadFullList(event: MouseEvent) {
|
||||
if (event.currentTarget instanceof Element) {
|
||||
if (event.target instanceof Element) {
|
||||
event.stopPropagation();
|
||||
const id = event.currentTarget.id;
|
||||
const button = event.target.closest('button') as HTMLElement;
|
||||
if (!button?.id) return;
|
||||
const id = button.id;
|
||||
const loader = document.getElementById('continuations')!;
|
||||
this.loadObserver = new MutationObserver(() => {
|
||||
this.applyToList(id, loader);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { FastAverageColor } from 'fast-average-color';
|
||||
import Color from 'color';
|
||||
import Color, { ColorInstance } from 'color';
|
||||
|
||||
import style from './style.css?inline';
|
||||
|
||||
@ -14,8 +14,8 @@ export default createPlugin<
|
||||
unknown,
|
||||
unknown,
|
||||
{
|
||||
color?: Color;
|
||||
darkColor?: Color;
|
||||
color?: ColorInstance;
|
||||
darkColor?: ColorInstance;
|
||||
|
||||
playerPage: HTMLElement | null;
|
||||
navBarBackground: HTMLElement | null;
|
||||
|
||||
@ -34,3 +34,12 @@
|
||||
margin: 0 auto !important;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Fix ambient mode overlapping other elements #2520 */
|
||||
.song-button.ytmusic-av-toggle, .video-button.ytmusic-av-toggle {
|
||||
z-index: 1;
|
||||
background-color: transparent;
|
||||
}
|
||||
#side-panel.side-panel.ytmusic-player-page {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
71
src/plugins/amuse/backend.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { t } from 'i18next';
|
||||
|
||||
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 { createBackend } from '@/utils';
|
||||
|
||||
import type { AmuseSongInfo } from './types';
|
||||
|
||||
const amusePort = 9863;
|
||||
|
||||
const formatSongInfo = (info: SongInfo) => {
|
||||
const formattedSongInfo: AmuseSongInfo = {
|
||||
player: {
|
||||
hasSong: !!(info.artist && info.title),
|
||||
isPaused: info.isPaused ?? false,
|
||||
seekbarCurrentPosition: info.elapsedSeconds ?? 0,
|
||||
},
|
||||
track: {
|
||||
duration: info.songDuration,
|
||||
title: info.title,
|
||||
author: info.artist,
|
||||
cover: info.imageSrc ?? '',
|
||||
url: info.url ?? '',
|
||||
id: info.videoId,
|
||||
isAdvertisement: false,
|
||||
},
|
||||
};
|
||||
return formattedSongInfo;
|
||||
};
|
||||
|
||||
export default createBackend({
|
||||
currentSongInfo: {} as SongInfo,
|
||||
app: null as Hono | null,
|
||||
server: null as ReturnType<typeof serve> | null,
|
||||
start() {
|
||||
registerCallback((songInfo) => {
|
||||
this.currentSongInfo = songInfo;
|
||||
});
|
||||
|
||||
this.app = new Hono();
|
||||
this.app.use('*', cors());
|
||||
this.app.get('/', (ctx) =>
|
||||
ctx.body(t('plugins.amuse.response.query'), 200),
|
||||
);
|
||||
|
||||
const queryAndApiHandler = (ctx: Context) => {
|
||||
return ctx.json(formatSongInfo(this.currentSongInfo), 200);
|
||||
};
|
||||
|
||||
this.app.get('/query', queryAndApiHandler);
|
||||
this.app.get('/api', queryAndApiHandler);
|
||||
|
||||
try {
|
||||
this.server = serve({
|
||||
fetch: this.app.fetch.bind(this.app),
|
||||
port: amusePort,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
|
||||
stop() {
|
||||
if (this.server) {
|
||||
this.server?.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
20
src/plugins/amuse/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { createPlugin } from '@/utils';
|
||||
import backend from './backend';
|
||||
import { t } from '@/i18n';
|
||||
|
||||
export interface MusicWidgetConfig {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export const defaultConfig: MusicWidgetConfig = {
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
export default createPlugin({
|
||||
name: () => t('plugins.amuse.name'),
|
||||
description: () => t('plugins.amuse.description'),
|
||||
addedVersion: '3.7.X',
|
||||
restartNeeded: true,
|
||||
config: defaultConfig,
|
||||
backend,
|
||||
});
|
||||
20
src/plugins/amuse/types.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export interface PlayerInfo {
|
||||
hasSong: boolean;
|
||||
isPaused: boolean;
|
||||
seekbarCurrentPosition: number;
|
||||
}
|
||||
|
||||
export interface TrackInfo {
|
||||
author: string;
|
||||
title: string;
|
||||
cover: string;
|
||||
duration: number;
|
||||
url: string;
|
||||
id: string;
|
||||
isAdvertisement: boolean;
|
||||
}
|
||||
|
||||
export interface AmuseSongInfo {
|
||||
player: PlayerInfo;
|
||||
track: TrackInfo;
|
||||
}
|
||||
@ -13,6 +13,7 @@ import { registerAuth, registerControl } from './routes';
|
||||
import { type APIServerConfig, AuthStrategy } from '../config';
|
||||
|
||||
import type { BackendType } from './types';
|
||||
import type { RepeatMode } from '@/types/datahost-get-state';
|
||||
|
||||
export const backend = createBackend<BackendType, APIServerConfig>({
|
||||
async start(ctx) {
|
||||
@ -23,6 +24,22 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
||||
this.songInfo = songInfo;
|
||||
});
|
||||
|
||||
ctx.ipc.on('ytmd:player-api-loaded', () => {
|
||||
ctx.ipc.send('ytmd:setup-time-changed-listener');
|
||||
ctx.ipc.send('ytmd:setup-repeat-changed-listener');
|
||||
ctx.ipc.send('ytmd:setup-volume-changed-listener');
|
||||
});
|
||||
|
||||
ctx.ipc.on(
|
||||
'ytmd:repeat-changed',
|
||||
(mode: RepeatMode) => (this.currentRepeatMode = mode),
|
||||
);
|
||||
|
||||
ctx.ipc.on(
|
||||
'ytmd:volume-changed',
|
||||
(newVolume: number) => (this.volume = newVolume),
|
||||
);
|
||||
|
||||
this.run(config.hostname, config.port);
|
||||
},
|
||||
stop() {
|
||||
@ -49,6 +66,12 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
||||
|
||||
this.app.use('*', cors());
|
||||
|
||||
// for web remote control
|
||||
this.app.use('*', async (ctx, next) => {
|
||||
ctx.header('Access-Control-Request-Private-Network', 'true');
|
||||
await next();
|
||||
});
|
||||
|
||||
// middlewares
|
||||
this.app.use('/api/*', async (ctx, next) => {
|
||||
if (config.authStrategy !== AuthStrategy.NONE) {
|
||||
@ -73,7 +96,13 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
||||
});
|
||||
|
||||
// routes
|
||||
registerControl(this.app, ctx, () => this.songInfo);
|
||||
registerControl(
|
||||
this.app,
|
||||
ctx,
|
||||
() => this.songInfo,
|
||||
() => this.currentRepeatMode,
|
||||
() => this.volume,
|
||||
);
|
||||
registerAuth(this.app, ctx);
|
||||
|
||||
// swagger
|
||||
|
||||
@ -5,21 +5,28 @@ import { ipcMain } from 'electron';
|
||||
import getSongControls from '@/providers/song-controls';
|
||||
|
||||
import {
|
||||
AuthHeadersSchema,
|
||||
type ResponseSongInfo,
|
||||
SongInfoSchema,
|
||||
GoForwardScheme,
|
||||
AddSongToQueueSchema,
|
||||
GoBackSchema,
|
||||
SwitchRepeatSchema,
|
||||
SetVolumeSchema,
|
||||
GoForwardScheme,
|
||||
MoveSongInQueueSchema,
|
||||
QueueParamsSchema,
|
||||
SearchSchema,
|
||||
SeekSchema,
|
||||
SetFullscreenSchema,
|
||||
SetQueueIndexSchema,
|
||||
SetVolumeSchema,
|
||||
SongInfoSchema,
|
||||
SwitchRepeatSchema,
|
||||
type ResponseSongInfo,
|
||||
} from '../scheme';
|
||||
|
||||
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';
|
||||
import type { HonoApp } from '../types';
|
||||
import type { QueueResponse } from '@/types/youtube-music-desktop-internal';
|
||||
import type { Context } from 'hono';
|
||||
|
||||
const API_VERSION = 'v1';
|
||||
|
||||
@ -102,14 +109,33 @@ const routes = {
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
seekTo: createRoute({
|
||||
method: 'post',
|
||||
path: `/api/${API_VERSION}/seek-to`,
|
||||
summary: 'seek',
|
||||
description: 'Seek to a specific time in the current song',
|
||||
request: {
|
||||
body: {
|
||||
description: 'seconds to seek to',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: SeekSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
},
|
||||
},
|
||||
}),
|
||||
goBack: createRoute({
|
||||
method: 'post',
|
||||
path: `/api/${API_VERSION}/go-back`,
|
||||
summary: 'go back',
|
||||
description: 'Move the current song back by a number of seconds',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
body: {
|
||||
description: 'seconds to go back',
|
||||
content: {
|
||||
@ -132,7 +158,6 @@ const routes = {
|
||||
summary: 'go forward',
|
||||
description: 'Move the current song forward by a number of seconds',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
body: {
|
||||
description: 'seconds to go forward',
|
||||
content: {
|
||||
@ -148,7 +173,24 @@ const routes = {
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
getShuffleState: createRoute({
|
||||
method: 'get',
|
||||
path: `/api/${API_VERSION}/shuffle`,
|
||||
summary: 'get shuffle state',
|
||||
description: 'Get the current shuffle state',
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Success',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: z.object({
|
||||
state: z.boolean().nullable(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
shuffle: createRoute({
|
||||
method: 'post',
|
||||
path: `/api/${API_VERSION}/shuffle`,
|
||||
@ -160,13 +202,30 @@ const routes = {
|
||||
},
|
||||
},
|
||||
}),
|
||||
repeatMode: createRoute({
|
||||
method: 'get',
|
||||
path: `/api/${API_VERSION}/repeat-mode`,
|
||||
summary: 'get current repeat mode',
|
||||
description: 'Get the current repeat mode (NONE, ALL, ONE)',
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Success',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: z.object({
|
||||
mode: z.enum(['ONE', 'NONE', 'ALL']).nullable(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
switchRepeat: createRoute({
|
||||
method: 'post',
|
||||
path: `/api/${API_VERSION}/switch-repeat`,
|
||||
summary: 'switch repeat',
|
||||
description: 'Switch the repeat mode',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
body: {
|
||||
description: 'number of times to click the repeat button',
|
||||
content: {
|
||||
@ -188,7 +247,6 @@ const routes = {
|
||||
summary: 'set volume',
|
||||
description: 'Set the volume of the player',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
body: {
|
||||
description: 'volume to set',
|
||||
content: {
|
||||
@ -204,13 +262,30 @@ const routes = {
|
||||
},
|
||||
},
|
||||
}),
|
||||
getVolumeState: createRoute({
|
||||
method: 'get',
|
||||
path: `/api/${API_VERSION}/volume`,
|
||||
summary: 'get volume state',
|
||||
description: 'Get the current volume state of the player',
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Success',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: z.object({
|
||||
state: z.number(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
setFullscreen: createRoute({
|
||||
method: 'post',
|
||||
path: `/api/${API_VERSION}/fullscreen`,
|
||||
summary: 'set fullscreen',
|
||||
description: 'Set the fullscreen state of the player',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
body: {
|
||||
description: 'fullscreen state',
|
||||
content: {
|
||||
@ -256,7 +331,8 @@ const routes = {
|
||||
},
|
||||
},
|
||||
}),
|
||||
queueInfo: createRoute({
|
||||
oldQueueInfo: createRoute({
|
||||
deprecated: true,
|
||||
method: 'get',
|
||||
path: `/api/${API_VERSION}/queue-info`,
|
||||
summary: 'get current queue info',
|
||||
@ -275,7 +351,8 @@ const routes = {
|
||||
},
|
||||
},
|
||||
}),
|
||||
songInfo: createRoute({
|
||||
oldSongInfo: createRoute({
|
||||
deprecated: true,
|
||||
method: 'get',
|
||||
path: `/api/${API_VERSION}/song-info`,
|
||||
summary: 'get current song info',
|
||||
@ -294,12 +371,167 @@ const routes = {
|
||||
},
|
||||
},
|
||||
}),
|
||||
songInfo: createRoute({
|
||||
method: 'get',
|
||||
path: `/api/${API_VERSION}/song`,
|
||||
summary: 'get current song info',
|
||||
description: 'Get the current song info',
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Success',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: SongInfoSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
204: {
|
||||
description: 'No song info',
|
||||
},
|
||||
},
|
||||
}),
|
||||
queueInfo: createRoute({
|
||||
method: 'get',
|
||||
path: `/api/${API_VERSION}/queue`,
|
||||
summary: 'get current queue info',
|
||||
description: 'Get the current queue info',
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Success',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: z.object({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
204: {
|
||||
description: 'No queue info',
|
||||
},
|
||||
},
|
||||
}),
|
||||
addSongToQueue: createRoute({
|
||||
method: 'post',
|
||||
path: `/api/${API_VERSION}/queue`,
|
||||
summary: 'add song to queue',
|
||||
description: 'Add a song to the queue',
|
||||
request: {
|
||||
body: {
|
||||
description: 'video id of the song to add',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: AddSongToQueueSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
},
|
||||
},
|
||||
}),
|
||||
moveSongInQueue: createRoute({
|
||||
method: 'patch',
|
||||
path: `/api/${API_VERSION}/queue/{index}`,
|
||||
summary: 'move song in queue',
|
||||
description: 'Move a song in the queue',
|
||||
request: {
|
||||
params: QueueParamsSchema,
|
||||
body: {
|
||||
description: 'index to move the song to',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: MoveSongInQueueSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
},
|
||||
},
|
||||
}),
|
||||
removeSongFromQueue: createRoute({
|
||||
method: 'delete',
|
||||
path: `/api/${API_VERSION}/queue/{index}`,
|
||||
summary: 'remove song from queue',
|
||||
description: 'Remove a song from the queue',
|
||||
request: {
|
||||
params: QueueParamsSchema,
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
},
|
||||
},
|
||||
}),
|
||||
setQueueIndex: createRoute({
|
||||
method: 'patch',
|
||||
path: `/api/${API_VERSION}/queue`,
|
||||
summary: 'set queue index',
|
||||
description: 'Set the current index of the queue',
|
||||
request: {
|
||||
body: {
|
||||
description: 'index to move the song to',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: SetQueueIndexSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
},
|
||||
},
|
||||
}),
|
||||
clearQueue: createRoute({
|
||||
method: 'delete',
|
||||
path: `/api/${API_VERSION}/queue`,
|
||||
summary: 'clear queue',
|
||||
description: 'Clear the queue',
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
},
|
||||
},
|
||||
}),
|
||||
search: createRoute({
|
||||
method: 'post',
|
||||
path: `/api/${API_VERSION}/search`,
|
||||
summary: 'search for a song',
|
||||
description: 'search for a song',
|
||||
request: {
|
||||
body: {
|
||||
description: 'search query',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: SearchSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Success',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: z.object({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export const register = (
|
||||
app: HonoApp,
|
||||
{ window }: BackendContext<APIServerConfig>,
|
||||
songInfoGetter: () => SongInfo | undefined,
|
||||
repeatModeGetter: () => RepeatMode | undefined,
|
||||
volumeGetter: () => number | undefined,
|
||||
) => {
|
||||
const controller = getSongControls(window);
|
||||
|
||||
@ -309,8 +541,8 @@ export const register = (
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
});
|
||||
app.openapi(routes.previous, (ctx) => {
|
||||
controller.previous();
|
||||
app.openapi(routes.next, (ctx) => {
|
||||
controller.next();
|
||||
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
@ -345,6 +577,13 @@ export const register = (
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
});
|
||||
app.openapi(routes.seekTo, (ctx) => {
|
||||
const { seconds } = ctx.req.valid('json');
|
||||
controller.seekTo(seconds);
|
||||
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
});
|
||||
app.openapi(routes.goBack, (ctx) => {
|
||||
const { seconds } = ctx.req.valid('json');
|
||||
controller.goBack(seconds);
|
||||
@ -359,12 +598,36 @@ export const register = (
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
});
|
||||
|
||||
app.openapi(routes.getShuffleState, async (ctx) => {
|
||||
const stateResponsePromise = new Promise<boolean>((resolve) => {
|
||||
ipcMain.once(
|
||||
'ytmd:get-shuffle-response',
|
||||
(_, isShuffled: boolean | undefined) => {
|
||||
return resolve(!!isShuffled);
|
||||
},
|
||||
);
|
||||
|
||||
controller.requestShuffleInformation();
|
||||
});
|
||||
|
||||
const isShuffled = await stateResponsePromise;
|
||||
|
||||
ctx.status(200);
|
||||
return ctx.json({ state: isShuffled });
|
||||
});
|
||||
|
||||
app.openapi(routes.shuffle, (ctx) => {
|
||||
controller.shuffle();
|
||||
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
});
|
||||
|
||||
app.openapi(routes.repeatMode, (ctx) => {
|
||||
ctx.status(200);
|
||||
return ctx.json({ mode: repeatModeGetter() ?? null });
|
||||
});
|
||||
app.openapi(routes.switchRepeat, (ctx) => {
|
||||
const { iteration } = ctx.req.valid('json');
|
||||
controller.switchRepeat(iteration);
|
||||
@ -379,6 +642,10 @@ export const register = (
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
});
|
||||
app.openapi(routes.getVolumeState, (ctx) => {
|
||||
ctx.status(200);
|
||||
return ctx.json({ state: volumeGetter() ?? 0 });
|
||||
});
|
||||
app.openapi(routes.setFullscreen, (ctx) => {
|
||||
const { state } = ctx.req.valid('json');
|
||||
controller.setFullscreen(state);
|
||||
@ -410,7 +677,26 @@ export const register = (
|
||||
ctx.status(200);
|
||||
return ctx.json({ state: fullscreen });
|
||||
});
|
||||
app.openapi(routes.queueInfo, async (ctx) => {
|
||||
|
||||
const songInfo = (ctx: Context) => {
|
||||
const info = songInfoGetter();
|
||||
|
||||
if (!info) {
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
}
|
||||
|
||||
const body = { ...info };
|
||||
delete body.image;
|
||||
|
||||
ctx.status(200);
|
||||
return ctx.json(body satisfies ResponseSongInfo);
|
||||
};
|
||||
app.openapi(routes.oldSongInfo, songInfo);
|
||||
app.openapi(routes.songInfo, songInfo);
|
||||
|
||||
// Queue
|
||||
const queueInfo = async (ctx: Context) => {
|
||||
const queueResponsePromise = new Promise<QueueResponse>((resolve) => {
|
||||
ipcMain.once('ytmd:get-queue-response', (_, queue: QueueResponse) => {
|
||||
return resolve(queue);
|
||||
@ -428,19 +714,50 @@ export const register = (
|
||||
|
||||
ctx.status(200);
|
||||
return ctx.json(info);
|
||||
};
|
||||
app.openapi(routes.oldQueueInfo, queueInfo);
|
||||
app.openapi(routes.queueInfo, queueInfo);
|
||||
|
||||
app.openapi(routes.addSongToQueue, (ctx) => {
|
||||
const { videoId, insertPosition } = ctx.req.valid('json');
|
||||
controller.addSongToQueue(videoId, { queueInsertPosition: insertPosition });
|
||||
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
});
|
||||
app.openapi(routes.songInfo, (ctx) => {
|
||||
const info = songInfoGetter();
|
||||
app.openapi(routes.moveSongInQueue, (ctx) => {
|
||||
const index = Number(ctx.req.param('index'));
|
||||
const { toIndex } = ctx.req.valid('json');
|
||||
controller.moveSongInQueue(index, toIndex);
|
||||
|
||||
if (!info) {
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
}
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
});
|
||||
app.openapi(routes.removeSongFromQueue, (ctx) => {
|
||||
const index = Number(ctx.req.param('index'));
|
||||
controller.removeSongFromQueue(index);
|
||||
|
||||
const body = { ...info };
|
||||
delete body.image;
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
});
|
||||
app.openapi(routes.setQueueIndex, (ctx) => {
|
||||
const { index } = ctx.req.valid('json');
|
||||
controller.setQueueIndex(index);
|
||||
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
});
|
||||
app.openapi(routes.clearQueue, (ctx) => {
|
||||
controller.clearQueue();
|
||||
|
||||
ctx.status(204);
|
||||
return ctx.body(null);
|
||||
});
|
||||
app.openapi(routes.search, async (ctx) => {
|
||||
const { query } = ctx.req.valid('json');
|
||||
const response = await controller.search(query);
|
||||
|
||||
ctx.status(200);
|
||||
return ctx.json(body satisfies ResponseSongInfo);
|
||||
return ctx.json(response as object);
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
import { z } from '@hono/zod-openapi';
|
||||
|
||||
export const AuthHeadersSchema = z.object({
|
||||
authorization: z.string().openapi({
|
||||
example: 'Bearer token',
|
||||
}),
|
||||
});
|
||||
|
||||
export type JWTPayload = z.infer<typeof JWTPayloadSchema>;
|
||||
export const JWTPayloadSchema = z.object({
|
||||
id: z.string(),
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
export * from './auth';
|
||||
export * from './song-info';
|
||||
export * from './seek';
|
||||
export * from './go-back';
|
||||
export * from './go-forward';
|
||||
export * from './switch-repeat';
|
||||
export * from './set-volume';
|
||||
export * from './set-fullscreen';
|
||||
export * from './queue';
|
||||
export * from './search';
|
||||
|
||||
19
src/plugins/api-server/backend/scheme/queue.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { z } from '@hono/zod-openapi';
|
||||
|
||||
export const QueueParamsSchema = z.object({
|
||||
index: z.coerce.number().int().nonnegative(),
|
||||
});
|
||||
|
||||
export const AddSongToQueueSchema = z.object({
|
||||
videoId: z.string(),
|
||||
insertPosition: z
|
||||
.enum(['INSERT_AT_END', 'INSERT_AFTER_CURRENT_VIDEO'])
|
||||
.optional()
|
||||
.default('INSERT_AT_END'),
|
||||
});
|
||||
export const MoveSongInQueueSchema = z.object({
|
||||
toIndex: z.number(),
|
||||
});
|
||||
export const SetQueueIndexSchema = z.object({
|
||||
index: z.number().int().nonnegative(),
|
||||
});
|
||||
5
src/plugins/api-server/backend/scheme/search.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { z } from '@hono/zod-openapi';
|
||||
|
||||
export const SearchSchema = z.object({
|
||||
query: z.string(),
|
||||
});
|
||||
5
src/plugins/api-server/backend/scheme/seek.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { z } from '@hono/zod-openapi';
|
||||
|
||||
export const SeekSchema = z.object({
|
||||
seconds: z.number(),
|
||||
});
|
||||
@ -3,6 +3,7 @@ import { serve } from '@hono/node-server';
|
||||
|
||||
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;
|
||||
@ -11,6 +12,8 @@ export type BackendType = {
|
||||
server?: ReturnType<typeof serve>;
|
||||
oldConfig?: APIServerConfig;
|
||||
songInfo?: SongInfo;
|
||||
currentRepeatMode?: RepeatMode;
|
||||
volume?: number;
|
||||
|
||||
init: (ctx: BackendContext<APIServerConfig>) => Promise<void>;
|
||||
run: (hostname: string, port: number) => void;
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
}
|
||||
|
||||
ytmusic-tabs {
|
||||
top: calc(var(--ytmusic-nav-bar-height) + var(--menu-bar-height, 36px));
|
||||
backdrop-filter: blur(8px) !important;
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
import { app, dialog, ipcMain } from 'electron';
|
||||
import { app, dialog } from 'electron';
|
||||
import { Client as DiscordClient } from '@xhayper/discord-rpc';
|
||||
import { dev } from 'electron-is';
|
||||
|
||||
import { ActivityType, GatewayActivityButton } from 'discord-api-types/v10';
|
||||
|
||||
import registerCallback, { type SongInfo } from '@/providers/song-info';
|
||||
import registerCallback, {
|
||||
type SongInfo,
|
||||
SongInfoEvent,
|
||||
} from '@/providers/song-info';
|
||||
import { createBackend, LoggerPrefix } from '@/utils';
|
||||
import { t } from '@/i18n';
|
||||
|
||||
@ -107,7 +110,7 @@ export const clear = () => {
|
||||
};
|
||||
|
||||
export const registerRefresh = (cb: () => void) => refreshCallbacks.push(cb);
|
||||
export const isConnected = () => info.rpc !== null;
|
||||
export const isConnected = () => info.rpc?.isConnected;
|
||||
|
||||
export const backend = createBackend<
|
||||
{
|
||||
@ -151,15 +154,14 @@ export const backend = createBackend<
|
||||
// @see https://discord.com/developers/docs/topics/gateway#activity-object
|
||||
// not all options are transfered through https://github.com/discordjs/RPC/blob/6f83d8d812c87cb7ae22064acd132600407d7d05/src/client.js#L518-530
|
||||
const hangulFillerUnicodeCharacter = '\u3164'; // This is an empty character
|
||||
if (songInfo.title.length < 2) {
|
||||
songInfo.title += hangulFillerUnicodeCharacter.repeat(
|
||||
2 - songInfo.title.length,
|
||||
);
|
||||
}
|
||||
if (songInfo.artist.length < 2) {
|
||||
songInfo.artist += hangulFillerUnicodeCharacter.repeat(
|
||||
2 - songInfo.title.length,
|
||||
);
|
||||
const paddedInfoKeys: (keyof SongInfo)[] = ['title', 'artist', 'album'];
|
||||
for (const key of paddedInfoKeys) {
|
||||
const keyLength = (songInfo[key] as string)?.length;
|
||||
if (keyLength < 2) {
|
||||
(songInfo[key] as string) += hangulFillerUnicodeCharacter.repeat(
|
||||
2 - keyLength,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// see https://github.com/th-ch/youtube-music/issues/1664
|
||||
@ -243,25 +245,28 @@ export const backend = createBackend<
|
||||
|
||||
// If the page is ready, register the callback
|
||||
ctx.window.once('ready-to-show', () => {
|
||||
let lastSongInfo: SongInfo;
|
||||
registerCallback((songInfo) => {
|
||||
lastSongInfo = songInfo;
|
||||
if (this.config) this.updateActivity(songInfo, this.config);
|
||||
});
|
||||
connect();
|
||||
let lastSent = Date.now();
|
||||
ipcMain.on('ytmd:time-changed', (_, t: number) => {
|
||||
const currentTime = Date.now();
|
||||
// if lastSent is more than 5 seconds ago, send the new time
|
||||
if (currentTime - lastSent > 5000) {
|
||||
lastSent = currentTime;
|
||||
if (lastSongInfo) {
|
||||
lastSongInfo.elapsedSeconds = t;
|
||||
if (this.config) this.updateActivity(lastSongInfo, this.config);
|
||||
registerCallback((songInfo, event) => {
|
||||
if (event !== SongInfoEvent.TimeChanged) {
|
||||
info.lastSongInfo = songInfo;
|
||||
if (this.config) this.updateActivity(songInfo, this.config);
|
||||
} else {
|
||||
const currentTime = Date.now();
|
||||
// if lastSent is more than 5 seconds ago, send the new time
|
||||
if (currentTime - lastSent > 5000) {
|
||||
lastSent = currentTime;
|
||||
if (songInfo) {
|
||||
info.lastSongInfo = songInfo;
|
||||
if (this.config) this.updateActivity(songInfo, this.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
connect();
|
||||
});
|
||||
ctx.ipc.on('ytmd:player-api-loaded', () =>
|
||||
ctx.ipc.send('ytmd:setup-time-changed-listener'),
|
||||
);
|
||||
app.on('window-all-closed', clear);
|
||||
},
|
||||
stop() {
|
||||
|
||||
@ -3,26 +3,22 @@ import { join } from 'node:path';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
|
||||
import { app, BrowserWindow, dialog, ipcMain } from 'electron';
|
||||
import {
|
||||
ClientType,
|
||||
Innertube,
|
||||
UniversalCache,
|
||||
Utils,
|
||||
YTNodes,
|
||||
} from 'youtubei.js';
|
||||
import { Innertube, UniversalCache, Utils, YTNodes } from 'youtubei.js';
|
||||
import is from 'electron-is';
|
||||
import filenamify from 'filenamify';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import { createFFmpeg } from '@ffmpeg.wasm/main';
|
||||
import NodeID3, { TagConstants } from 'node-id3';
|
||||
|
||||
import { Window } from 'happy-dom';
|
||||
import { BG, type BgConfig } from 'bgutils-js';
|
||||
|
||||
import {
|
||||
cropMaxWidth,
|
||||
getFolder,
|
||||
sendFeedback as sendFeedback_,
|
||||
setBadge,
|
||||
} from './utils';
|
||||
|
||||
import { fetchFromGenius } from '@/plugins/lyrics-genius/main';
|
||||
import { isEnabled } from '@/config/plugins';
|
||||
import registerCallback, {
|
||||
@ -30,23 +26,20 @@ import registerCallback, {
|
||||
getImage,
|
||||
MediaType,
|
||||
type SongInfo,
|
||||
SongInfoEvent,
|
||||
} from '@/providers/song-info';
|
||||
import { getNetFetchAsFetch } from '@/plugins/utils/main';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import { YoutubeFormatList, type Preset, DefaultPresetList } from '../types';
|
||||
import { DefaultPresetList, type Preset, YoutubeFormatList } from '../types';
|
||||
|
||||
import type { DownloaderPluginConfig } from '../index';
|
||||
|
||||
import type { BackendContext } from '@/types/contexts';
|
||||
|
||||
import type { FormatOptions } from 'youtubei.js/dist/src/types/FormatUtils';
|
||||
import type PlayerErrorMessage from 'youtubei.js/dist/src/parser/classes/PlayerErrorMessage';
|
||||
import type { Playlist } from 'youtubei.js/dist/src/parser/ytmusic';
|
||||
import type { VideoInfo } from 'youtubei.js/dist/src/parser/youtube';
|
||||
import type TrackInfo from 'youtubei.js/dist/src/parser/ytmusic/TrackInfo';
|
||||
|
||||
import type { GetPlayerResponse } from '@/types/get-player-response';
|
||||
|
||||
type CustomSongInfo = SongInfo & { trackId?: string };
|
||||
@ -62,13 +55,33 @@ let yt: Innertube;
|
||||
let win: BrowserWindow;
|
||||
let playingUrl: string;
|
||||
|
||||
const isYouTubeMusicPremium = async () => {
|
||||
const upgradeBtnIconPathData = (await win.webContents.executeJavaScript(
|
||||
'document.querySelector(\'iron-iconset-svg[name="yt-sys-icons"] #youtube_music_monochrome\')?.firstChild?.getAttribute("d")?.substring(0, 15)',
|
||||
)) as string | null;
|
||||
|
||||
// Fallback to non-premium if the icon is not found
|
||||
if (!upgradeBtnIconPathData) return false;
|
||||
|
||||
const selector = `ytmusic-guide-entry-renderer:has(> tp-yt-paper-item > yt-icon path[d^="${upgradeBtnIconPathData}"])`;
|
||||
|
||||
return (await win.webContents.executeJavaScript(
|
||||
`!document.querySelector('${selector}')`,
|
||||
)) as boolean;
|
||||
};
|
||||
|
||||
const sendError = (error: Error, source?: string) => {
|
||||
win.setProgressBar(-1); // Close progress bar
|
||||
setBadge(0); // Close badge
|
||||
sendFeedback_(win); // Reset feedback
|
||||
|
||||
const songNameMessage = source ? `\nin ${source}` : '';
|
||||
const cause = error.cause ? `\n\n${String(error.cause)}` : '';
|
||||
const cause = error.cause
|
||||
? `\n\n${
|
||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string,@typescript-eslint/restrict-template-expressions
|
||||
error.cause instanceof Error ? error.cause.toString() : error.cause
|
||||
}`
|
||||
: '';
|
||||
const message = `${error.toString()}${songNameMessage}${cause}`;
|
||||
|
||||
console.error(message);
|
||||
@ -108,6 +121,66 @@ export const onMainLoad = async ({
|
||||
generate_session_locally: true,
|
||||
fetch: getNetFetchAsFetch(),
|
||||
});
|
||||
|
||||
const requestKey = 'O43z0dpjhgX20SCx4KAo';
|
||||
const visitorData = yt.session.context.client.visitorData;
|
||||
|
||||
if (visitorData) {
|
||||
const cleanUp = (context: Partial<typeof globalThis>) => {
|
||||
delete context.window;
|
||||
delete context.document;
|
||||
};
|
||||
|
||||
try {
|
||||
const [width, height] = win.getSize();
|
||||
// emulate jsdom using linkedom
|
||||
const window = new Window({
|
||||
width,
|
||||
height,
|
||||
console,
|
||||
});
|
||||
const document = window.document;
|
||||
|
||||
Object.assign(globalThis, {
|
||||
window,
|
||||
document,
|
||||
});
|
||||
|
||||
const bgConfig: BgConfig = {
|
||||
fetch: getNetFetchAsFetch(),
|
||||
globalObj: globalThis,
|
||||
identifier: visitorData,
|
||||
requestKey,
|
||||
};
|
||||
|
||||
const bgChallenge = await BG.Challenge.create(bgConfig);
|
||||
const interpreterJavascript =
|
||||
bgChallenge?.interpreterJavascript
|
||||
.privateDoNotAccessOrElseSafeScriptWrappedValue;
|
||||
|
||||
if (interpreterJavascript) {
|
||||
// This is a workaround to run the interpreterJavascript code
|
||||
// Maybe there is a better way to do this (e.g. https://github.com/Siubaak/sval ?)
|
||||
// eslint-disable-next-line @typescript-eslint/no-implied-eval,@typescript-eslint/no-unsafe-call
|
||||
new Function(interpreterJavascript)();
|
||||
|
||||
const poTokenResult = await BG.PoToken.generate({
|
||||
program: bgChallenge.program,
|
||||
globalName: bgChallenge.globalName,
|
||||
bgConfig,
|
||||
}).finally(() => {
|
||||
cleanUp(globalThis);
|
||||
});
|
||||
|
||||
yt.session.po_token = poTokenResult.poToken;
|
||||
} else {
|
||||
cleanUp(globalThis);
|
||||
}
|
||||
} catch {
|
||||
cleanUp(globalThis);
|
||||
}
|
||||
}
|
||||
|
||||
ipc.handle('download-song', (url: string) => downloadSong(url));
|
||||
ipc.on('ytmd:video-src-changed', (data: GetPlayerResponse) => {
|
||||
playingUrl = data.microformat.microformatDataRenderer.urlCanonical;
|
||||
@ -174,7 +247,12 @@ function downloadSongOnFinishSetup({
|
||||
|
||||
const defaultDownloadFolder = app.getPath('downloads');
|
||||
|
||||
registerCallback((songInfo: SongInfo) => {
|
||||
registerCallback((songInfo: SongInfo, event) => {
|
||||
if (event === SongInfoEvent.TimeChanged) {
|
||||
const elapsedSeconds = songInfo.elapsedSeconds ?? 0;
|
||||
if (elapsedSeconds > time) time = elapsedSeconds;
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!songInfo.isPaused &&
|
||||
songInfo.url !== currentUrl &&
|
||||
@ -213,10 +291,6 @@ function downloadSongOnFinishSetup({
|
||||
ipcMain.on('ytmd:player-api-loaded', () => {
|
||||
ipc.send('ytmd:setup-time-changed-listener');
|
||||
});
|
||||
|
||||
ipcMain.on('ytmd:time-changed', (_, t: number) => {
|
||||
if (t > time) time = t;
|
||||
});
|
||||
}
|
||||
|
||||
async function downloadSongUnsafe(
|
||||
@ -306,7 +380,7 @@ async function downloadSongUnsafe(
|
||||
}
|
||||
|
||||
const downloadOptions: FormatOptions = {
|
||||
type: 'audio', // Audio, video or video+audio
|
||||
type: (await isYouTubeMusicPremium()) ? 'audio' : 'video+audio', // Audio, video or video+audio
|
||||
quality: 'best', // Best, bestefficiency, 144p, 240p, 480p, 720p and so on.
|
||||
format: 'any', // Media container format
|
||||
};
|
||||
@ -578,20 +652,17 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!playlist ||
|
||||
!playlist.items ||
|
||||
playlist.items.length === 0 ||
|
||||
!playlist.header ||
|
||||
!('title' in playlist.header)
|
||||
) {
|
||||
if (!playlist || !playlist.items || playlist.items.length === 0) {
|
||||
sendError(
|
||||
new Error(t('plugins.downloader.backend.feedback.playlist-is-empty')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const normalPlaylistTitle = playlist.header?.title?.text;
|
||||
const normalPlaylistTitle =
|
||||
playlist.header && 'title' in playlist.header
|
||||
? playlist.header?.title?.text
|
||||
: undefined;
|
||||
const playlistTitle =
|
||||
normalPlaylistTitle ??
|
||||
playlist.page.contents_memo
|
||||
@ -767,13 +838,7 @@ const getMetadata = (info: TrackInfo): CustomSongInfo => ({
|
||||
|
||||
// This is used to bypass age restrictions
|
||||
const getAndroidTvInfo = async (id: string): Promise<VideoInfo> => {
|
||||
const innertube = await Innertube.create({
|
||||
client_type: ClientType.TV_EMBEDDED,
|
||||
generate_session_locally: true,
|
||||
retrieve_player: true,
|
||||
fetch: getNetFetchAsFetch(),
|
||||
});
|
||||
// GetInfo 404s with the bypass, so we use getBasicInfo instead
|
||||
// that's fine as we only need the streaming data
|
||||
return await innertube.getBasicInfo(id, 'TV_EMBEDDED');
|
||||
return await yt.getBasicInfo(id, 'TV_EMBEDDED');
|
||||
};
|
||||
|
||||
@ -8,6 +8,8 @@ import { LoggerPrefix } from '@/utils';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import { defaultTrustedTypePolicy } from '@/utils/trusted-types';
|
||||
|
||||
import { ElementFromHtml } from '../utils/renderer';
|
||||
|
||||
import type { RendererContext } from '@/types/contexts';
|
||||
@ -107,7 +109,10 @@ export const onRendererLoad = ({
|
||||
|
||||
ipc.on('downloader-feedback', (feedback: string) => {
|
||||
if (progress) {
|
||||
progress.innerHTML = feedback || t('plugins.downloader.templates.button');
|
||||
const targetHtml = feedback || t('plugins.downloader.templates.button');
|
||||
(progress.innerHTML as string | TrustedHTML) = defaultTrustedTypePolicy
|
||||
? defaultTrustedTypePolicy.createHTML(targetHtml)
|
||||
: targetHtml;
|
||||
} else {
|
||||
console.warn(
|
||||
LoggerPrefix,
|
||||
|
||||
81
src/plugins/equalizer/index.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { createPlugin } from '@/utils';
|
||||
import { t } from '@/i18n';
|
||||
import { MenuContext } from '@/types/contexts';
|
||||
import { MenuTemplate } from '@/menu';
|
||||
import { defaultPresets, presetConfigs, Preset, FilterConfig } from './presets';
|
||||
|
||||
export type EqualizerPluginConfig = {
|
||||
enabled: boolean;
|
||||
filters: FilterConfig[];
|
||||
presets: { [preset in Preset]: boolean };
|
||||
};
|
||||
|
||||
let appliedFilters: BiquadFilterNode[] = [];
|
||||
|
||||
export default createPlugin({
|
||||
name: () => t('plugins.equalizer.name'),
|
||||
description: () => t('plugins.equalizer.description'),
|
||||
restartNeeded: false,
|
||||
addedVersion: '3.7.X',
|
||||
config: {
|
||||
enabled: false,
|
||||
filters: [],
|
||||
presets: { 'bass-booster': false },
|
||||
} as EqualizerPluginConfig,
|
||||
menu: async ({
|
||||
getConfig,
|
||||
setConfig,
|
||||
}: MenuContext<EqualizerPluginConfig>): Promise<MenuTemplate> => {
|
||||
const config = await getConfig();
|
||||
|
||||
return [
|
||||
{
|
||||
label: t('plugins.equalizer.menu.presets.label'),
|
||||
type: 'submenu',
|
||||
submenu: defaultPresets.map((preset) => ({
|
||||
label: t(`plugins.equalizer.menu.presets.list.${preset}`),
|
||||
type: 'radio',
|
||||
checked: config.presets[preset],
|
||||
click() {
|
||||
setConfig({
|
||||
presets: { ...config.presets, [preset]: !config.presets[preset] },
|
||||
});
|
||||
},
|
||||
})),
|
||||
},
|
||||
];
|
||||
},
|
||||
renderer: {
|
||||
async start({ getConfig }) {
|
||||
const config = await getConfig();
|
||||
|
||||
document.addEventListener(
|
||||
'ytmd:audio-can-play',
|
||||
({ detail: { audioSource, audioContext } }) => {
|
||||
const filtersToApply = config.filters.concat(
|
||||
defaultPresets
|
||||
.filter((preset) => config.presets[preset])
|
||||
.map((preset) => presetConfigs[preset]),
|
||||
);
|
||||
filtersToApply.forEach((filter) => {
|
||||
const biquadFilter = audioContext.createBiquadFilter();
|
||||
biquadFilter.type = filter.type;
|
||||
biquadFilter.frequency.value = filter.frequency; // filter frequency in Hz
|
||||
biquadFilter.Q.value = filter.Q;
|
||||
biquadFilter.gain.value = filter.gain; // filter gain in dB
|
||||
|
||||
audioSource.connect(biquadFilter);
|
||||
biquadFilter.connect(audioContext.destination);
|
||||
|
||||
appliedFilters.push(biquadFilter);
|
||||
});
|
||||
},
|
||||
{ once: true, passive: true },
|
||||
);
|
||||
},
|
||||
stop() {
|
||||
appliedFilters.forEach((filter) => filter.disconnect());
|
||||
appliedFilters = [];
|
||||
},
|
||||
},
|
||||
});
|
||||
18
src/plugins/equalizer/presets.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export const defaultPresets = ['bass-booster'] as const;
|
||||
export type Preset = (typeof defaultPresets)[number];
|
||||
|
||||
export type FilterConfig = {
|
||||
type: BiquadFilterType;
|
||||
frequency: number;
|
||||
Q: number;
|
||||
gain: number;
|
||||
};
|
||||
|
||||
export const presetConfigs: Record<Preset, FilterConfig> = {
|
||||
'bass-booster': {
|
||||
type: 'lowshelf',
|
||||
frequency: 80,
|
||||
Q: 100,
|
||||
gain: 12.0,
|
||||
},
|
||||
};
|
||||
@ -5,9 +5,12 @@
|
||||
|
||||
/* youtube-music style */
|
||||
ytmusic-app-layout {
|
||||
overflow: scroll;
|
||||
overflow: auto scroll;
|
||||
height: calc(100vh - var(--menu-bar-height, 36px));
|
||||
margin-top: var(--menu-bar-height, 36px) !important;
|
||||
|
||||
/* fixes laggy list scrolling in large playlists */
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
ytmusic-app-layout#layout {
|
||||
--ytmusic-nav-bar-offset: 0px;
|
||||
@ -72,3 +75,8 @@ ytmusic-app-layout ytmusic-player-page[is-mweb-modernization-enabled] .side-pane
|
||||
html {
|
||||
scrollbar-color: unset;
|
||||
}
|
||||
|
||||
/* fixes scrollbar lagging behind in large playlists */
|
||||
ytmusic-browse-response .ytmusic-responsive-list-item-renderer {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ export default createPlugin({
|
||||
config: {
|
||||
enabled: false,
|
||||
},
|
||||
backend() {
|
||||
backend({ ipc }) {
|
||||
const secToMilisec = (t?: number) =>
|
||||
t ? Math.round(Number(t) * 1e3) : undefined;
|
||||
const previousStatePaused = null;
|
||||
@ -65,6 +65,10 @@ export default createPlugin({
|
||||
});
|
||||
};
|
||||
|
||||
ipc.on('ytmd:player-api-loaded', () =>
|
||||
ipc.send('ytmd:setup-time-changed-listener'),
|
||||
);
|
||||
|
||||
registerCallback((songInfo) => {
|
||||
if (!songInfo.title && !songInfo.artist) {
|
||||
return;
|
||||
|
||||
@ -2,6 +2,8 @@ import { LoggerPrefix } from '@/utils';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import { defaultTrustedTypePolicy } from '@/utils/trusted-types';
|
||||
|
||||
import type { SongInfo } from '@/providers/song-info';
|
||||
import type { RendererContext } from '@/types/contexts';
|
||||
import type { LyricsGeniusPluginConfig } from '@/plugins/lyrics-genius/index';
|
||||
@ -10,7 +12,7 @@ export const onRendererLoad = ({
|
||||
ipc: { invoke, on },
|
||||
}: RendererContext<LyricsGeniusPluginConfig>) => {
|
||||
const setLyrics = (lyricsContainer: Element, lyrics: string | null) => {
|
||||
lyricsContainer.innerHTML = `
|
||||
const targetHtml = `
|
||||
<div id="contents" class="style-scope ytmusic-section-list-renderer description ytmusic-description-shelf-renderer genius-lyrics">
|
||||
${
|
||||
lyrics?.replaceAll(/\r\n|\r|\n/g, '<br/>') ??
|
||||
@ -20,6 +22,10 @@ export const onRendererLoad = ({
|
||||
<yt-formatted-string class="footer style-scope ytmusic-description-shelf-renderer" style="align-self: baseline">
|
||||
</yt-formatted-string>
|
||||
`;
|
||||
(lyricsContainer.innerHTML as string | TrustedHTML) =
|
||||
defaultTrustedTypePolicy
|
||||
? defaultTrustedTypePolicy.createHTML(targetHtml)
|
||||
: targetHtml;
|
||||
|
||||
if (lyrics) {
|
||||
const footer = lyricsContainer.querySelector('.footer');
|
||||
|
||||
@ -18,8 +18,8 @@ export interface Section {
|
||||
|
||||
export interface Hit {
|
||||
highlights: Highlight[];
|
||||
index: Index;
|
||||
type: Index;
|
||||
index: ResultType;
|
||||
type: ResultType;
|
||||
result: Result;
|
||||
}
|
||||
|
||||
@ -35,14 +35,10 @@ export interface Range {
|
||||
end: number;
|
||||
}
|
||||
|
||||
export enum Index {
|
||||
Album = 'album',
|
||||
Lyric = 'lyric',
|
||||
Song = 'song',
|
||||
}
|
||||
export type ResultType = 'song' | 'album' | 'lyric';
|
||||
|
||||
export interface Result {
|
||||
_type: Index;
|
||||
_type: ResultType;
|
||||
annotation_count?: number;
|
||||
api_path: string;
|
||||
artist_names?: string;
|
||||
|
||||
117
src/plugins/music-together/api/guest.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import { setQueue } from '../store/queue';
|
||||
|
||||
import { ConnectionEventUnion, MusicTogetherConfig, VideoData } from '../types';
|
||||
import { setStatus } from '../store/status';
|
||||
import { IPC } from '../constants';
|
||||
import { Connection } from '../connection';
|
||||
|
||||
import type { AppElement } from '@/types/queue';
|
||||
import type { RendererContext } from '@/types/contexts';
|
||||
|
||||
type BuildListenerOptions = {
|
||||
ipc: RendererContext<MusicTogetherConfig>['ipc'];
|
||||
app: AppElement;
|
||||
};
|
||||
export const Guest = {
|
||||
buildListener: (_: Connection, { ipc, app }: BuildListenerOptions) => {
|
||||
const listener = async (event: ConnectionEventUnion) => {
|
||||
switch (event.type) {
|
||||
case 'ADD_SONGS': {
|
||||
await ipc.invoke(
|
||||
IPC.addSongToQueue,
|
||||
event.payload.videoList.map((v) => v.videoId),
|
||||
{
|
||||
index: event.payload.index,
|
||||
},
|
||||
);
|
||||
|
||||
setQueue('queue', (queue) => {
|
||||
const result: VideoData[] = [...queue];
|
||||
|
||||
if (event.payload.index) {
|
||||
result.splice(event.payload.index, 0, ...event.payload.videoList);
|
||||
} else {
|
||||
result.push(...event.payload.videoList);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'REMOVE_SONG': {
|
||||
await ipc.invoke(IPC.removeSongFromQueue, event.payload.index);
|
||||
|
||||
setQueue('queue', (queue) => {
|
||||
const result: VideoData[] = [...queue];
|
||||
result.splice(event.payload.index, 1);
|
||||
return result;
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'MOVE_SONG': {
|
||||
await ipc.invoke(
|
||||
IPC.moveSongInQueue,
|
||||
event.payload.fromIndex,
|
||||
event.payload.toIndex,
|
||||
);
|
||||
|
||||
setQueue('queue', (queue) => {
|
||||
const result: VideoData[] = [...queue];
|
||||
const [removed] = result.splice(event.payload.fromIndex, 1);
|
||||
result.splice(event.payload.toIndex, 0, removed);
|
||||
return result;
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'IDENTIFY': {
|
||||
console.warn('Music Together [Guest]: Not allowed Event', event);
|
||||
break;
|
||||
}
|
||||
case 'SYNC_USER': {
|
||||
setStatus('users', event.payload?.users ?? []);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'PERMISSION': {
|
||||
const permission = event.payload;
|
||||
if (!permission) break;
|
||||
|
||||
setStatus('permission', permission);
|
||||
break;
|
||||
}
|
||||
case 'SYNC_QUEUE': {
|
||||
await ipc.invoke(IPC.clearQueue);
|
||||
await ipc.invoke(
|
||||
IPC.addSongToQueue,
|
||||
event.payload?.videoList.map((v) => v.videoId),
|
||||
{
|
||||
queueInsertPosition: 'INSERT_AT_END',
|
||||
},
|
||||
);
|
||||
setQueue('queue', event.payload?.videoList ?? []);
|
||||
break;
|
||||
}
|
||||
case 'SYNC_PROGRESS': {
|
||||
if (typeof event.payload?.progress === 'number') {
|
||||
app.playerApi?.seekTo(event.payload.progress);
|
||||
}
|
||||
if (app.playerApi?.getPlayerState() !== event.payload?.state) {
|
||||
if (event.payload?.state === 2) app.playerApi?.pauseVideo();
|
||||
if (event.payload?.state === 1) app.playerApi?.playVideo();
|
||||
}
|
||||
if (typeof event.payload?.index === 'number') {
|
||||
await ipc.invoke(IPC.setQueueIndex, event.payload.index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.warn('Music Together [Host]: Unknown Event', event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return listener;
|
||||
},
|
||||
};
|
||||
140
src/plugins/music-together/api/host.ts
Normal file
@ -0,0 +1,140 @@
|
||||
import { DataConnection } from 'peerjs';
|
||||
|
||||
import { RendererContext } from '@/types/contexts';
|
||||
|
||||
import { queue } from '@/plugins/music-together/store/queue';
|
||||
|
||||
import { AppElement } from '@/types/queue';
|
||||
|
||||
import { ConnectionEventUnion, MusicTogetherConfig } from '../types';
|
||||
import { setStatus, status } from '../store/status';
|
||||
import { IPC } from '../constants';
|
||||
import { Connection } from '../connection';
|
||||
|
||||
type BuildListenerOptions = {
|
||||
ipc: RendererContext<MusicTogetherConfig>['ipc'];
|
||||
app: AppElement;
|
||||
};
|
||||
export const Host = {
|
||||
buildListener: (conn: Connection, { ipc, app }: BuildListenerOptions) => {
|
||||
const listener = async (
|
||||
event: ConnectionEventUnion,
|
||||
dataConnection?: DataConnection,
|
||||
) => {
|
||||
switch (event.type) {
|
||||
case 'ADD_SONGS': {
|
||||
if (dataConnection && status.permission === 'host-only') return;
|
||||
|
||||
await ipc.invoke(
|
||||
IPC.addSongToQueue,
|
||||
event.payload.videoList.map((v) => v.videoId),
|
||||
{
|
||||
index: event.payload.index,
|
||||
},
|
||||
);
|
||||
console.log('ADD_SONGS', event);
|
||||
await conn?.broadcast(event.type, event.payload);
|
||||
break;
|
||||
}
|
||||
case 'REMOVE_SONG': {
|
||||
if (dataConnection && status.permission === 'host-only') return;
|
||||
|
||||
await ipc.invoke(IPC.removeSongFromQueue, event.payload.index);
|
||||
await conn?.broadcast(event.type, event.payload);
|
||||
break;
|
||||
}
|
||||
case 'MOVE_SONG': {
|
||||
if (dataConnection && status.permission === 'host-only') {
|
||||
// await conn.broadcast('SYNC_QUEUE', {
|
||||
// videoList: queue?.videoList ?? [],
|
||||
// });
|
||||
break;
|
||||
}
|
||||
|
||||
await ipc.invoke(
|
||||
IPC.moveSongInQueue,
|
||||
event.payload.fromIndex,
|
||||
event.payload.toIndex,
|
||||
);
|
||||
await conn?.broadcast(event.type, event.payload);
|
||||
break;
|
||||
}
|
||||
case 'IDENTIFY': {
|
||||
const newUser = event.payload?.user;
|
||||
if (!newUser) return;
|
||||
|
||||
// api?.toastService?.show(
|
||||
// t('plugins.music-together.toast.user-connected', {
|
||||
// name: event.payload.profile.name,
|
||||
// }),
|
||||
// );
|
||||
|
||||
setStatus('users', (users) => [...users, newUser]);
|
||||
await conn?.broadcast('SYNC_USER', {
|
||||
users: status.users,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'SYNC_USER': {
|
||||
await conn?.broadcast('SYNC_USER', {
|
||||
users: status.users,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case 'PERMISSION': {
|
||||
await conn?.broadcast('PERMISSION', status.permission);
|
||||
break;
|
||||
}
|
||||
case 'SYNC_QUEUE': {
|
||||
await conn?.broadcast('SYNC_QUEUE', {
|
||||
videoList: queue.queue,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'SYNC_PROGRESS': {
|
||||
let permissionLevel = 0;
|
||||
if (status.permission === 'all') permissionLevel = 2;
|
||||
if (status.permission === 'playlist') permissionLevel = 1;
|
||||
if (status.permission === 'host-only') permissionLevel = 0;
|
||||
if (!conn) permissionLevel = 3;
|
||||
|
||||
if (permissionLevel >= 2) {
|
||||
if (typeof event.payload?.progress === 'number') {
|
||||
const currentTime = app.playerApi?.getCurrentTime() ?? 0;
|
||||
const offset = Math.abs(event.payload.progress - currentTime);
|
||||
if (offset > 3)
|
||||
app.playerApi?.seekTo(event.payload.progress + offset);
|
||||
}
|
||||
if (app.playerApi?.getPlayerState() !== event.payload?.state) {
|
||||
if (event.payload?.state === 2) app.playerApi?.pauseVideo();
|
||||
if (event.payload?.state === 1) app.playerApi?.playVideo();
|
||||
}
|
||||
}
|
||||
if (permissionLevel >= 1) {
|
||||
if (typeof event.payload?.index === 'number') {
|
||||
await ipc.invoke(IPC.setQueueIndex, event.payload.index);
|
||||
}
|
||||
}
|
||||
|
||||
await conn?.broadcast('SYNC_PROGRESS', event.payload);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.warn('Music Together [Host]: Unknown Event', event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.after) {
|
||||
const now = event.after.shift();
|
||||
if (now) {
|
||||
now.after = event.after;
|
||||
await listener(now, dataConnection);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return listener;
|
||||
},
|
||||
};
|
||||
0
src/plugins/music-together/api/queue.ts
Normal file
53
src/plugins/music-together/backend.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import prompt from 'custom-electron-prompt';
|
||||
|
||||
import { MusicTogetherConfig } from './types';
|
||||
|
||||
import promptOptions from '@/providers/prompt-options';
|
||||
|
||||
import getSongControls from '@/providers/song-controls';
|
||||
|
||||
import { IPC } from './constants';
|
||||
|
||||
import type { BackendContext } from '@/types/contexts';
|
||||
|
||||
export const onMainLoad = ({
|
||||
ipc,
|
||||
window,
|
||||
}: BackendContext<MusicTogetherConfig>) => {
|
||||
const controller = getSongControls(window);
|
||||
|
||||
ipc.handle(IPC.prompt, async (title: string, label: string) =>
|
||||
prompt({
|
||||
title,
|
||||
label,
|
||||
type: 'input',
|
||||
...promptOptions(),
|
||||
}),
|
||||
);
|
||||
|
||||
ipc.handle(IPC.play, () => controller.play());
|
||||
ipc.handle(IPC.pause, () => controller.pause());
|
||||
ipc.handle(IPC.previous, () => controller.previous());
|
||||
ipc.handle(IPC.next, () => controller.next());
|
||||
ipc.handle(IPC.seekTo, (seconds: number) => controller.seekTo(seconds));
|
||||
ipc.handle(
|
||||
IPC.addSongToQueue,
|
||||
(
|
||||
ids: string | string[],
|
||||
options: {
|
||||
queueInsertPosition?: 'INSERT_AT_END' | 'INSERT_AFTER_CURRENT_VIDEO';
|
||||
index?: number;
|
||||
},
|
||||
) => controller.addSongToQueue(ids, options),
|
||||
);
|
||||
ipc.handle(IPC.removeSongFromQueue, (index: number) =>
|
||||
controller.removeSongFromQueue(index),
|
||||
);
|
||||
ipc.handle(IPC.moveSongInQueue, (fromIndex: number, toIndex: number) =>
|
||||
controller.moveSongInQueue(fromIndex, toIndex),
|
||||
);
|
||||
ipc.handle(IPC.clearQueue, () => controller.clearQueue());
|
||||
ipc.handle(IPC.setQueueIndex, (index: number) =>
|
||||
controller.setQueueIndex(index),
|
||||
);
|
||||
};
|
||||
@ -1,26 +1,6 @@
|
||||
import { DataConnection, Peer } from 'peerjs';
|
||||
|
||||
import type { Permission, Profile, VideoData } from './types';
|
||||
|
||||
export type ConnectionEventMap = {
|
||||
ADD_SONGS: { videoList: VideoData[]; index?: number };
|
||||
REMOVE_SONG: { index: number };
|
||||
MOVE_SONG: { fromIndex: number; toIndex: number };
|
||||
IDENTIFY: { profile: Profile } | undefined;
|
||||
SYNC_PROFILE: { profiles: Record<string, Profile> } | undefined;
|
||||
SYNC_QUEUE: { videoList: VideoData[] } | undefined;
|
||||
SYNC_PROGRESS:
|
||||
| { progress?: number; state?: number; index?: number }
|
||||
| undefined;
|
||||
PERMISSION: Permission | undefined;
|
||||
};
|
||||
export type ConnectionEventUnion = {
|
||||
[Event in keyof ConnectionEventMap]: {
|
||||
type: Event;
|
||||
payload: ConnectionEventMap[Event];
|
||||
after?: ConnectionEventUnion[];
|
||||
};
|
||||
}[keyof ConnectionEventMap];
|
||||
import { ConnectedState, ConnectionEventMap, ConnectionEventUnion } from './types';
|
||||
|
||||
type PromiseUtil<T> = {
|
||||
promise: Promise<T>;
|
||||
@ -32,10 +12,10 @@ export type ConnectionListener = (
|
||||
event: ConnectionEventUnion,
|
||||
conn: DataConnection,
|
||||
) => void;
|
||||
export type ConnectionMode = 'host' | 'guest' | 'disconnected';
|
||||
|
||||
export class Connection {
|
||||
private peer: Peer;
|
||||
private _mode: ConnectionMode = 'disconnected';
|
||||
private _state: ConnectedState = 'disconnected';
|
||||
private connections: Record<string, DataConnection> = {};
|
||||
|
||||
private waitOpen: PromiseUtil<string> = {} as PromiseUtil<string>;
|
||||
@ -51,15 +31,15 @@ export class Connection {
|
||||
});
|
||||
|
||||
this.peer.on('open', (id) => {
|
||||
this._mode = 'host';
|
||||
this._state = 'connecting';
|
||||
this.waitOpen.resolve(id);
|
||||
});
|
||||
this.peer.on('connection', (conn) => {
|
||||
this._mode = 'host';
|
||||
this._state = 'host';
|
||||
this.registerConnection(conn);
|
||||
});
|
||||
this.peer.on('error', (err) => {
|
||||
this._mode = 'disconnected';
|
||||
this._state = 'disconnected';
|
||||
|
||||
this.waitOpen.reject(err);
|
||||
this.connectionListeners.forEach((listener) => listener());
|
||||
@ -73,16 +53,16 @@ export class Connection {
|
||||
}
|
||||
|
||||
async connect(id: string) {
|
||||
this._mode = 'guest';
|
||||
this._state = 'guest';
|
||||
const conn = this.peer.connect(id);
|
||||
await this.registerConnection(conn);
|
||||
return conn;
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this._mode === 'disconnected') throw new Error('Already disconnected');
|
||||
if (this._state === 'disconnected') throw new Error('Already disconnected');
|
||||
|
||||
this._mode = 'disconnected';
|
||||
this._state = 'disconnected';
|
||||
this.connections = {};
|
||||
this.peer.destroy();
|
||||
}
|
||||
@ -92,8 +72,8 @@ export class Connection {
|
||||
return this.peer.id;
|
||||
}
|
||||
|
||||
public get mode() {
|
||||
return this._mode;
|
||||
public get state() {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public getConnections() {
|
||||
@ -121,7 +101,7 @@ export class Connection {
|
||||
private async registerConnection(conn: DataConnection) {
|
||||
return new Promise<DataConnection>((resolve, reject) => {
|
||||
this.peer.once('error', (err) => {
|
||||
this._mode = 'disconnected';
|
||||
this._state = 'disconnected';
|
||||
|
||||
reject(err);
|
||||
this.connectionListeners.forEach((listener) => listener());
|
||||
@ -133,6 +113,12 @@ export class Connection {
|
||||
this.connectionListeners.forEach((listener) => listener(conn));
|
||||
|
||||
conn.on('data', (data) => {
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (
|
||||
!data ||
|
||||
typeof data !== 'object' ||
|
||||
@ -140,7 +126,7 @@ export class Connection {
|
||||
!('payload' in data) ||
|
||||
!data.type
|
||||
) {
|
||||
console.warn('Music Together: Invalid data', data);
|
||||
console.warn('Music Together: Invalid data', data, typeof data);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
13
src/plugins/music-together/constants.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export const IPC = {
|
||||
prompt: 'music-together:prompt',
|
||||
play: 'music-together:play',
|
||||
pause: 'music-together:pause',
|
||||
previous: 'music-together:previous',
|
||||
next: 'music-together:next',
|
||||
seekTo: 'music-together:seekTo',
|
||||
addSongToQueue: 'music-together:addSongToQueue',
|
||||
removeSongFromQueue: 'music-together:removeSongFromQueue',
|
||||
moveSongInQueue: 'music-together:moveSongInQueue',
|
||||
clearQueue: 'music-together:clearQueue',
|
||||
setQueueIndex: 'music-together:setQueueIndex',
|
||||
};
|
||||
31
src/plugins/music-together/context/RendererContext.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { createContext, JSX, splitProps, useContext } from 'solid-js';
|
||||
|
||||
import { MusicTogetherConfig } from '../types';
|
||||
|
||||
import { RendererContext } from '@/types/contexts';
|
||||
|
||||
export type RendererContextContextType = {
|
||||
context: RendererContext<MusicTogetherConfig>;
|
||||
};
|
||||
export const RendererContextContext =
|
||||
createContext<RendererContextContextType>();
|
||||
|
||||
export type RendererContextProviderProps = RendererContextContextType & {
|
||||
children: JSX.Element;
|
||||
};
|
||||
export const RendererContextProvider = (
|
||||
props: RendererContextProviderProps,
|
||||
) => {
|
||||
const [local, left] = splitProps(props, ['children']);
|
||||
return (
|
||||
<RendererContextContext.Provider value={left}>
|
||||
{local.children}
|
||||
</RendererContextContext.Provider>
|
||||
);
|
||||
};
|
||||
export const useRendererContext = () => {
|
||||
const context = useContext(RendererContextContext);
|
||||
if (!context) throw Error('RendererContextProvider not found');
|
||||
|
||||
return context.context;
|
||||
};
|
||||
22
src/plugins/music-together/context/ToastContext.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { createContext, JSX, useContext } from 'solid-js';
|
||||
|
||||
import { ToastService } from '@/types/queue';
|
||||
|
||||
export type ToastContextType = {
|
||||
service: ToastService;
|
||||
};
|
||||
export const ToastContext = createContext<ToastContextType>();
|
||||
|
||||
export type ToastProviderProps = ToastContextType & {
|
||||
children: JSX.Element;
|
||||
};
|
||||
export const ToastProvider = (props: ToastProviderProps) => (
|
||||
<ToastContext.Provider value={props}>{props.children}</ToastContext.Provider>
|
||||
);
|
||||
export const useToast = () => {
|
||||
const context = useContext(ToastContext);
|
||||
|
||||
return (message: string) => {
|
||||
context?.service.show(message);
|
||||
};
|
||||
};
|
||||
@ -1,148 +0,0 @@
|
||||
import { ElementFromHtml } from '@/plugins/utils/renderer';
|
||||
|
||||
import itemHTML from './templates/item.html?raw';
|
||||
import popupHTML from './templates/popup.html?raw';
|
||||
|
||||
type Placement =
|
||||
| 'top'
|
||||
| 'bottom'
|
||||
| 'right'
|
||||
| 'left'
|
||||
| 'center'
|
||||
| 'middle'
|
||||
| 'center-middle'
|
||||
| 'top-left'
|
||||
| 'top-right'
|
||||
| 'bottom-left'
|
||||
| 'bottom-right';
|
||||
type PopupItem =
|
||||
| (ItemRendererProps & { type: 'item' })
|
||||
| { type: 'divider' }
|
||||
| { type: 'custom'; element: HTMLElement };
|
||||
|
||||
type PopupProps = {
|
||||
data: PopupItem[];
|
||||
anchorAt?: Placement;
|
||||
popupAt?: Placement;
|
||||
};
|
||||
export const Popup = (props: PopupProps) => {
|
||||
const popup = ElementFromHtml(popupHTML);
|
||||
const container = popup.querySelector<HTMLElement>(
|
||||
'.music-together-popup-container',
|
||||
)!;
|
||||
const items = props.data
|
||||
.map((props) => {
|
||||
if (props.type === 'item')
|
||||
return {
|
||||
type: 'item' as const,
|
||||
...ItemRenderer(props),
|
||||
};
|
||||
if (props.type === 'divider')
|
||||
return {
|
||||
type: 'divider' as const,
|
||||
element: ElementFromHtml(
|
||||
'<div class="music-together-divider horizontal"></div>',
|
||||
),
|
||||
};
|
||||
if (props.type === 'custom')
|
||||
return {
|
||||
type: 'custom' as const,
|
||||
element: props.element,
|
||||
};
|
||||
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
container.append(...items.map(({ element }) => element));
|
||||
popup.style.setProperty('opacity', '0');
|
||||
popup.style.setProperty('pointer-events', 'none');
|
||||
|
||||
document.body.append(popup);
|
||||
|
||||
return {
|
||||
element: popup,
|
||||
container,
|
||||
items,
|
||||
|
||||
show(x: number, y: number, anchor?: HTMLElement) {
|
||||
let left = x;
|
||||
let top = y;
|
||||
|
||||
if (anchor) {
|
||||
if (props.anchorAt?.includes('right')) left += anchor.clientWidth;
|
||||
if (props.anchorAt?.includes('bottom')) top += anchor.clientHeight;
|
||||
if (props.anchorAt?.includes('center')) left += anchor.clientWidth / 2;
|
||||
if (props.anchorAt?.includes('middle')) top += anchor.clientHeight / 2;
|
||||
}
|
||||
|
||||
if (props.popupAt?.includes('right')) left -= popup.clientWidth;
|
||||
if (props.popupAt?.includes('bottom')) top -= popup.clientHeight;
|
||||
if (props.popupAt?.includes('center')) left -= popup.clientWidth / 2;
|
||||
if (props.popupAt?.includes('middle')) top -= popup.clientHeight / 2;
|
||||
|
||||
popup.style.setProperty('left', `${left}px`);
|
||||
popup.style.setProperty('top', `${top}px`);
|
||||
popup.style.setProperty('opacity', '1');
|
||||
popup.style.setProperty('pointer-events', 'unset');
|
||||
|
||||
setTimeout(() => {
|
||||
const onClose = (event: MouseEvent) => {
|
||||
const isPopupClick = event
|
||||
.composedPath()
|
||||
.some((element) => element === popup);
|
||||
if (!isPopupClick) {
|
||||
this.dismiss();
|
||||
document.removeEventListener('click', onClose);
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', onClose);
|
||||
}, 16);
|
||||
},
|
||||
showAtAnchor(anchor: HTMLElement) {
|
||||
const { x, y } = anchor.getBoundingClientRect();
|
||||
this.show(x, y, anchor);
|
||||
},
|
||||
|
||||
isShowing() {
|
||||
return popup.style.getPropertyValue('opacity') === '1';
|
||||
},
|
||||
|
||||
dismiss() {
|
||||
popup.style.setProperty('opacity', '0');
|
||||
popup.style.setProperty('pointer-events', 'none');
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type ItemRendererProps = {
|
||||
id?: string;
|
||||
icon?: Element;
|
||||
text: string;
|
||||
onClick?: () => void;
|
||||
};
|
||||
export const ItemRenderer = (props: ItemRendererProps) => {
|
||||
const element = ElementFromHtml(itemHTML);
|
||||
const iconContainer = element.querySelector<HTMLElement>('div.icon')!;
|
||||
const textContainer = element.querySelector<HTMLElement>('div.text')!;
|
||||
if (props.icon) iconContainer.appendChild(props.icon);
|
||||
textContainer.append(props.text);
|
||||
|
||||
if (props.onClick) {
|
||||
element.addEventListener('click', () => {
|
||||
props.onClick?.();
|
||||
});
|
||||
}
|
||||
if (props.id) element.id = props.id;
|
||||
|
||||
return {
|
||||
element,
|
||||
setIcon(icon: Element) {
|
||||
iconContainer.replaceChildren(icon);
|
||||
},
|
||||
setText(text: string) {
|
||||
textContainer.replaceChildren(text);
|
||||
},
|
||||
id: props.id,
|
||||
};
|
||||
};
|
||||
@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
|
||||
<path d="M480-640 280-440l56 56 104-103v407h80v-407l104 103 56-56-200-200ZM146-260q-32-49-49-105T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 59-17 115t-49 105l-58-58q22-37 33-78t11-84q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 43 11 84t33 78l-58 58Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 408 B |
@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
|
||||
<path
|
||||
d="M280-400q-33 0-56.5-23.5T200-480q0-33 23.5-56.5T280-560q33 0 56.5 23.5T360-480q0 33-23.5 56.5T280-400Zm0 160q-100 0-170-70T40-480q0-100 70-170t170-70q67 0 121.5 33t86.5 87h352l120 120-180 180-80-60-80 60-85-60h-47q-32 54-86.5 87T280-240Zm0-80q56 0 98.5-34t56.5-86h125l58 41 82-61 71 55 75-75-40-40H435q-14-52-56.5-86T280-640q-66 0-113 47t-47 113q0 66 47 113t113 47Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 480 B |
@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
|
||||
<path d="M560-160q-66 0-113-47t-47-113q0-66 47-113t113-47q23 0 42.5 5.5T640-458v-342h240v120H720v360q0 66-47 113t-113 47ZM80-320q0-99 38-186.5T221-659q65-65 152.5-103T560-800v80q-82 0-155 31.5t-127.5 86q-54.5 54.5-86 127T160-320H80Zm160 0q0-66 25.5-124.5t69-102Q378-590 436-615t124-25v80q-100 0-170 70t-70 170h-80Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 416 B |
@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
|
||||
<path
|
||||
d="M792-56 686-160H260q-92 0-156-64T40-380q0-77 47.5-137T210-594q3-8 6-15.5t6-16.5L56-792l56-56 736 736-56 56ZM260-240h346L284-562q-2 11-3 21t-1 21h-20q-58 0-99 41t-41 99q0 58 41 99t99 41Zm185-161Zm419 191-58-56q17-14 25.5-32.5T840-340q0-42-29-71t-71-29h-60v-80q0-83-58.5-141.5T480-720q-27 0-52 6.5T380-693l-58-58q35-24 74.5-36.5T480-800q117 0 198.5 81.5T760-520q69 8 114.5 59.5T920-340q0 39-15 72.5T864-210ZM593-479Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 529 B |
@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
|
||||
<path d="M440-120v-240h80v80h320v80H520v80h-80Zm-320-80v-80h240v80H120Zm160-160v-80H120v-80h160v-80h80v240h-80Zm160-80v-80h400v80H440Zm160-160v-240h80v80h160v80H680v80h-80Zm-480-80v-80h400v80H120Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 298 B |
@ -1,84 +1,10 @@
|
||||
import prompt from 'custom-electron-prompt';
|
||||
|
||||
import { DataConnection } from 'peerjs';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
import { createPlugin } from '@/utils';
|
||||
import promptOptions from '@/providers/prompt-options';
|
||||
|
||||
import {
|
||||
getDefaultProfile,
|
||||
type Permission,
|
||||
type Profile,
|
||||
type VideoData,
|
||||
} from './types';
|
||||
import { Queue } from './queue';
|
||||
import { Connection, type ConnectionEventUnion } from './connection';
|
||||
import { createHostPopup } from './ui/host';
|
||||
import { createGuestPopup } from './ui/guest';
|
||||
import { createSettingPopup } from './ui/setting';
|
||||
import { onMainLoad } from './backend';
|
||||
import { onRendererLoad } from './src';
|
||||
|
||||
import settingHTML from './templates/setting.html?raw';
|
||||
import style from './style.css?inline';
|
||||
|
||||
import type { YoutubePlayer } from '@/types/youtube-player';
|
||||
import type { RendererContext } from '@/types/contexts';
|
||||
import type { VideoDataChanged } from '@/types/video-data-changed';
|
||||
import type { AppElement } from '@/types/queue';
|
||||
|
||||
type RawAccountData = {
|
||||
accountName: {
|
||||
runs: { text: string }[];
|
||||
};
|
||||
accountPhoto: {
|
||||
thumbnails: { url: string; width: number; height: number }[];
|
||||
};
|
||||
settingsEndpoint: unknown;
|
||||
manageAccountTitle: unknown;
|
||||
trackingParams: string;
|
||||
channelHandle: {
|
||||
runs: { text: string }[];
|
||||
};
|
||||
};
|
||||
|
||||
export default createPlugin<
|
||||
unknown,
|
||||
unknown,
|
||||
{
|
||||
connection?: Connection;
|
||||
ipc?: RendererContext<never>['ipc'];
|
||||
api: AppElement | null;
|
||||
queue?: Queue;
|
||||
playerApi?: YoutubePlayer;
|
||||
showPrompt: (title: string, label: string) => Promise<string>;
|
||||
popups: {
|
||||
host: ReturnType<typeof createHostPopup>;
|
||||
guest: ReturnType<typeof createGuestPopup>;
|
||||
setting: ReturnType<typeof createSettingPopup>;
|
||||
};
|
||||
elements: {
|
||||
setting: HTMLElement;
|
||||
icon: SVGElement;
|
||||
spinner: HTMLElement;
|
||||
};
|
||||
stateInterval?: number;
|
||||
updateNext: boolean;
|
||||
ignoreChange: boolean;
|
||||
rollbackInjector?: () => void;
|
||||
me?: Omit<Profile, 'id'>;
|
||||
profiles: Record<string, Profile>;
|
||||
permission: Permission;
|
||||
videoChangeListener: (event: CustomEvent<VideoDataChanged>) => void;
|
||||
videoStateChangeListener: () => void;
|
||||
onHost: () => Promise<boolean>;
|
||||
onJoin: () => Promise<boolean>;
|
||||
onStop: () => void;
|
||||
putProfile: (id: string, profile?: Profile) => void;
|
||||
showSpinner: () => void;
|
||||
hideSpinner: () => void;
|
||||
initMyProfile: () => void;
|
||||
}
|
||||
>({
|
||||
export default createPlugin({
|
||||
name: () => t('plugins.music-together.name'),
|
||||
description: () => t('plugins.music-together.description'),
|
||||
restartNeeded: false,
|
||||
@ -86,755 +12,9 @@ export default createPlugin<
|
||||
config: {
|
||||
enabled: false,
|
||||
},
|
||||
stylesheets: [style],
|
||||
backend({ ipc }) {
|
||||
ipc.handle('music-together:prompt', async (title: string, label: string) =>
|
||||
prompt({
|
||||
title,
|
||||
label,
|
||||
type: 'input',
|
||||
...promptOptions(),
|
||||
}),
|
||||
);
|
||||
},
|
||||
stylesheets: [],
|
||||
backend: onMainLoad,
|
||||
renderer: {
|
||||
updateNext: false,
|
||||
ignoreChange: false,
|
||||
permission: 'playlist',
|
||||
popups: {} as {
|
||||
host: ReturnType<typeof createHostPopup>;
|
||||
guest: ReturnType<typeof createGuestPopup>;
|
||||
setting: ReturnType<typeof createSettingPopup>;
|
||||
},
|
||||
elements: {} as {
|
||||
setting: HTMLElement;
|
||||
icon: SVGElement;
|
||||
spinner: HTMLElement;
|
||||
},
|
||||
profiles: {},
|
||||
showPrompt: () => Promise.resolve(''),
|
||||
api: null,
|
||||
|
||||
/* events */
|
||||
videoChangeListener(event: CustomEvent<VideoDataChanged>) {
|
||||
if (event.detail.name === 'dataloaded' || this.updateNext) {
|
||||
if (this.connection?.mode === 'host') {
|
||||
const videoList: VideoData[] =
|
||||
this.queue?.flatItems.map(
|
||||
(it) =>
|
||||
({
|
||||
videoId: it!.videoId,
|
||||
ownerId: this.connection!.id,
|
||||
}) satisfies VideoData,
|
||||
) ?? [];
|
||||
|
||||
this.queue?.setVideoList(videoList, false);
|
||||
this.queue?.syncQueueOwner();
|
||||
this.connection.broadcast('SYNC_QUEUE', {
|
||||
videoList,
|
||||
});
|
||||
|
||||
this.updateNext = event.detail.name === 'dataloaded';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
videoStateChangeListener() {
|
||||
if (this.connection?.mode !== 'guest') return;
|
||||
if (this.ignoreChange) return;
|
||||
if (this.permission !== 'all') return;
|
||||
|
||||
const state = this.playerApi?.getPlayerState();
|
||||
if (state !== 1 && state !== 2) return;
|
||||
|
||||
this.connection.broadcast('SYNC_PROGRESS', {
|
||||
// progress: this.playerApi?.getCurrentTime(),
|
||||
state: this.playerApi?.getPlayerState(),
|
||||
// index: this.queue?.selectedIndex ?? 0,
|
||||
});
|
||||
},
|
||||
|
||||
/* connection */
|
||||
async onHost() {
|
||||
this.connection = new Connection();
|
||||
const wait = await this.connection.waitForReady().catch(() => null);
|
||||
if (!wait) return false;
|
||||
|
||||
if (!this.me) this.me = getDefaultProfile(this.connection.id);
|
||||
const rawItems =
|
||||
this.queue?.flatItems?.map(
|
||||
(it) =>
|
||||
({
|
||||
videoId: it!.videoId,
|
||||
ownerId: this.connection!.id,
|
||||
}) satisfies VideoData,
|
||||
) ?? [];
|
||||
this.queue?.setOwner({
|
||||
id: this.connection.id,
|
||||
...this.me,
|
||||
});
|
||||
this.queue?.setVideoList(rawItems, false);
|
||||
this.queue?.syncQueueOwner();
|
||||
this.queue?.initQueue();
|
||||
this.queue?.injection();
|
||||
|
||||
this.profiles = {};
|
||||
this.connection.onConnections((connection) => {
|
||||
if (!connection) {
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.disconnected'),
|
||||
);
|
||||
this.onStop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!connection.open) {
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.user-disconnected', {
|
||||
name: this.profiles[connection.peer]?.name,
|
||||
}),
|
||||
);
|
||||
this.putProfile(connection.peer, undefined);
|
||||
}
|
||||
});
|
||||
this.putProfile(this.connection.id, {
|
||||
id: this.connection.id,
|
||||
...this.me,
|
||||
});
|
||||
|
||||
const listener = async (
|
||||
event: ConnectionEventUnion,
|
||||
conn?: DataConnection,
|
||||
) => {
|
||||
this.ignoreChange = true;
|
||||
|
||||
switch (event.type) {
|
||||
case 'ADD_SONGS': {
|
||||
if (conn && this.permission === 'host-only') return;
|
||||
|
||||
await this.queue?.addVideos(
|
||||
event.payload.videoList,
|
||||
event.payload.index,
|
||||
);
|
||||
await this.connection?.broadcast('ADD_SONGS', event.payload);
|
||||
break;
|
||||
}
|
||||
case 'REMOVE_SONG': {
|
||||
if (conn && this.permission === 'host-only') return;
|
||||
|
||||
this.queue?.removeVideo(event.payload.index);
|
||||
await this.connection?.broadcast('REMOVE_SONG', event.payload);
|
||||
break;
|
||||
}
|
||||
case 'MOVE_SONG': {
|
||||
if (conn && this.permission === 'host-only') {
|
||||
await this.connection?.broadcast('SYNC_QUEUE', {
|
||||
videoList: this.queue?.videoList ?? [],
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
this.queue?.moveItem(
|
||||
event.payload.fromIndex,
|
||||
event.payload.toIndex,
|
||||
);
|
||||
await this.connection?.broadcast('MOVE_SONG', event.payload);
|
||||
break;
|
||||
}
|
||||
case 'IDENTIFY': {
|
||||
if (!event.payload || !conn) {
|
||||
console.warn(
|
||||
'Music Together [Host]: Received "IDENTIFY" event without payload or connection',
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.user-connected', {
|
||||
name: event.payload.profile.name,
|
||||
}),
|
||||
);
|
||||
this.putProfile(conn.peer, event.payload.profile);
|
||||
break;
|
||||
}
|
||||
case 'SYNC_PROFILE': {
|
||||
await this.connection?.broadcast('SYNC_PROFILE', {
|
||||
profiles: this.profiles,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case 'PERMISSION': {
|
||||
await this.connection?.broadcast('PERMISSION', this.permission);
|
||||
this.popups.guest.setPermission(this.permission);
|
||||
this.popups.host.setPermission(this.permission);
|
||||
this.popups.setting.setPermission(this.permission);
|
||||
break;
|
||||
}
|
||||
case 'SYNC_QUEUE': {
|
||||
await this.connection?.broadcast('SYNC_QUEUE', {
|
||||
videoList: this.queue?.videoList ?? [],
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'SYNC_PROGRESS': {
|
||||
let permissionLevel = 0;
|
||||
if (this.permission === 'all') permissionLevel = 2;
|
||||
if (this.permission === 'playlist') permissionLevel = 1;
|
||||
if (this.permission === 'host-only') permissionLevel = 0;
|
||||
if (!conn) permissionLevel = 3;
|
||||
|
||||
if (permissionLevel >= 2) {
|
||||
if (typeof event.payload?.progress === 'number') {
|
||||
const currentTime = this.playerApi?.getCurrentTime() ?? 0;
|
||||
if (Math.abs(event.payload.progress - currentTime) > 3)
|
||||
this.playerApi?.seekTo(event.payload.progress);
|
||||
}
|
||||
if (this.playerApi?.getPlayerState() !== event.payload?.state) {
|
||||
if (event.payload?.state === 2) this.playerApi?.pauseVideo();
|
||||
if (event.payload?.state === 1) this.playerApi?.playVideo();
|
||||
}
|
||||
}
|
||||
if (permissionLevel >= 1) {
|
||||
if (typeof event.payload?.index === 'number') {
|
||||
const nowIndex = this.queue?.selectedIndex ?? 0;
|
||||
|
||||
if (nowIndex !== event.payload.index) {
|
||||
this.queue?.setIndex(event.payload.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.warn('Music Together [Host]: Unknown Event', event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.after) {
|
||||
const now = event.after.shift();
|
||||
if (now) {
|
||||
now.after = event.after;
|
||||
await listener(now, conn);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.connection.on(listener);
|
||||
this.queue?.on(listener);
|
||||
|
||||
setTimeout(() => {
|
||||
this.ignoreChange = false;
|
||||
}, 16); // wait 1 frame
|
||||
return true;
|
||||
},
|
||||
|
||||
async onJoin() {
|
||||
this.connection = new Connection();
|
||||
const wait = await this.connection.waitForReady().catch(() => null);
|
||||
if (!wait) return false;
|
||||
|
||||
this.profiles = {};
|
||||
|
||||
const id = await this.showPrompt(
|
||||
t('plugins.music-together.name'),
|
||||
t('plugins.music-together.dialog.enter-host'),
|
||||
);
|
||||
if (typeof id !== 'string') return false;
|
||||
|
||||
const connection = await this.connection.connect(id).catch(() => false);
|
||||
if (!connection) return false;
|
||||
this.connection.onConnections((connection) => {
|
||||
if (!connection?.open) {
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.disconnected'),
|
||||
);
|
||||
this.onStop();
|
||||
}
|
||||
});
|
||||
|
||||
let resolveIgnore: number | null = null;
|
||||
const listener = async (event: ConnectionEventUnion) => {
|
||||
this.ignoreChange = true;
|
||||
switch (event.type) {
|
||||
case 'ADD_SONGS': {
|
||||
await this.queue?.addVideos(
|
||||
event.payload.videoList,
|
||||
event.payload.index,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'REMOVE_SONG': {
|
||||
this.queue?.removeVideo(event.payload.index);
|
||||
break;
|
||||
}
|
||||
case 'MOVE_SONG': {
|
||||
this.queue?.moveItem(
|
||||
event.payload.fromIndex,
|
||||
event.payload.toIndex,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'IDENTIFY': {
|
||||
console.warn(
|
||||
'Music Together [Guest]: Received "IDENTIFY" event from guest',
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'SYNC_QUEUE': {
|
||||
if (Array.isArray(event.payload?.videoList)) {
|
||||
await this.queue?.setVideoList(event.payload.videoList);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'SYNC_PROFILE': {
|
||||
if (!event.payload) {
|
||||
console.warn(
|
||||
'Music Together [Guest]: Received "SYNC_PROFILE" event without payload',
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
Object.entries(event.payload.profiles).forEach(([id, profile]) => {
|
||||
this.putProfile(id, profile);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'SYNC_PROGRESS': {
|
||||
if (typeof event.payload?.progress === 'number') {
|
||||
const currentTime = this.playerApi?.getCurrentTime() ?? 0;
|
||||
if (Math.abs(event.payload.progress - currentTime) > 3)
|
||||
this.playerApi?.seekTo(event.payload.progress);
|
||||
}
|
||||
if (this.playerApi?.getPlayerState() !== event.payload?.state) {
|
||||
if (event.payload?.state === 2) this.playerApi?.pauseVideo();
|
||||
if (event.payload?.state === 1) this.playerApi?.playVideo();
|
||||
}
|
||||
if (typeof event.payload?.index === 'number') {
|
||||
const nowIndex = this.queue?.selectedIndex ?? 0;
|
||||
|
||||
if (nowIndex !== event.payload.index) {
|
||||
this.queue?.setIndex(event.payload.index);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'PERMISSION': {
|
||||
if (!event.payload) {
|
||||
console.warn(
|
||||
'Music Together [Guest]: Received "PERMISSION" event without payload',
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
this.permission = event.payload;
|
||||
this.popups.guest.setPermission(this.permission);
|
||||
this.popups.host.setPermission(this.permission);
|
||||
this.popups.setting.setPermission(this.permission);
|
||||
|
||||
const permissionLabel = t(
|
||||
`plugins.music-together.menu.permission.${this.permission}`,
|
||||
);
|
||||
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.permission-changed', {
|
||||
permission: permissionLabel,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.warn('Music Together [Guest]: Unknown Event', event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof resolveIgnore === 'number') clearTimeout(resolveIgnore);
|
||||
resolveIgnore = window.setTimeout(() => {
|
||||
this.ignoreChange = false;
|
||||
}, 16); // wait 1 frame
|
||||
};
|
||||
|
||||
this.connection.on(listener);
|
||||
this.queue?.on(async (event: ConnectionEventUnion) => {
|
||||
this.ignoreChange = true;
|
||||
switch (event.type) {
|
||||
case 'ADD_SONGS': {
|
||||
await this.connection?.broadcast('ADD_SONGS', event.payload);
|
||||
await this.connection?.broadcast('SYNC_QUEUE', undefined);
|
||||
break;
|
||||
}
|
||||
case 'REMOVE_SONG': {
|
||||
await this.connection?.broadcast('REMOVE_SONG', event.payload);
|
||||
break;
|
||||
}
|
||||
case 'MOVE_SONG': {
|
||||
await this.connection?.broadcast('MOVE_SONG', event.payload);
|
||||
await this.connection?.broadcast('SYNC_QUEUE', undefined);
|
||||
break;
|
||||
}
|
||||
case 'SYNC_PROGRESS': {
|
||||
if (this.permission === 'host-only')
|
||||
await this.connection?.broadcast('SYNC_QUEUE', undefined);
|
||||
else
|
||||
await this.connection?.broadcast('SYNC_PROGRESS', event.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof resolveIgnore === 'number') clearTimeout(resolveIgnore);
|
||||
resolveIgnore = window.setTimeout(() => {
|
||||
this.ignoreChange = false;
|
||||
}, 16); // wait 1 frame
|
||||
});
|
||||
|
||||
if (!this.me) this.me = getDefaultProfile(this.connection.id);
|
||||
this.queue?.injection();
|
||||
this.queue?.setOwner({
|
||||
id: this.connection.id,
|
||||
...this.me,
|
||||
});
|
||||
|
||||
const progress = Array.from(
|
||||
document.querySelectorAll<
|
||||
HTMLElement & {
|
||||
_update: (...args: unknown[]) => void;
|
||||
}
|
||||
>('tp-yt-paper-progress'),
|
||||
);
|
||||
const rollbackList = progress.map((progress) => {
|
||||
const original = progress._update;
|
||||
progress._update = (...args) => {
|
||||
const now = args[0];
|
||||
|
||||
if (this.permission === 'all' && typeof now === 'number') {
|
||||
const currentTime = this.playerApi?.getCurrentTime() ?? 0;
|
||||
if (Math.abs(now - currentTime) > 3)
|
||||
this.connection?.broadcast('SYNC_PROGRESS', {
|
||||
progress: now,
|
||||
state: this.playerApi?.getPlayerState(),
|
||||
});
|
||||
}
|
||||
|
||||
original.call(progress, ...args);
|
||||
};
|
||||
|
||||
return () => {
|
||||
progress._update = original;
|
||||
};
|
||||
});
|
||||
this.rollbackInjector = () => {
|
||||
rollbackList.forEach((rollback) => rollback());
|
||||
};
|
||||
|
||||
this.connection.broadcast('IDENTIFY', {
|
||||
profile: {
|
||||
id: this.connection.id,
|
||||
handleId: this.me.handleId,
|
||||
name: this.me.name,
|
||||
thumbnail: this.me.thumbnail,
|
||||
},
|
||||
});
|
||||
|
||||
this.connection.broadcast('SYNC_PROFILE', undefined);
|
||||
this.connection.broadcast('PERMISSION', undefined);
|
||||
|
||||
this.queue?.clear();
|
||||
this.queue?.syncQueueOwner();
|
||||
this.queue?.initQueue();
|
||||
|
||||
this.connection.broadcast('SYNC_QUEUE', undefined);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
onStop() {
|
||||
this.connection?.disconnect();
|
||||
this.queue?.rollbackInjection();
|
||||
this.queue?.removeQueueOwner();
|
||||
if (this.rollbackInjector) {
|
||||
this.rollbackInjector();
|
||||
this.rollbackInjector = undefined;
|
||||
}
|
||||
|
||||
this.profiles = {};
|
||||
this.popups.host.setUsers(Object.values(this.profiles));
|
||||
this.popups.guest.setUsers(Object.values(this.profiles));
|
||||
|
||||
this.popups.host.dismiss();
|
||||
this.popups.guest.dismiss();
|
||||
this.popups.setting.dismiss();
|
||||
},
|
||||
|
||||
/* methods */
|
||||
putProfile(id: string, profile?: Profile) {
|
||||
if (profile === undefined) {
|
||||
delete this.profiles[id];
|
||||
} else {
|
||||
this.profiles[id] = profile;
|
||||
}
|
||||
|
||||
this.popups.host.setUsers(Object.values(this.profiles));
|
||||
this.popups.guest.setUsers(Object.values(this.profiles));
|
||||
},
|
||||
|
||||
showSpinner() {
|
||||
this.elements.icon.style.setProperty('display', 'none');
|
||||
this.elements.spinner.removeAttribute('hidden');
|
||||
this.elements.spinner.setAttribute('active', '');
|
||||
},
|
||||
|
||||
hideSpinner() {
|
||||
this.elements.icon.style.removeProperty('display');
|
||||
this.elements.spinner.removeAttribute('active');
|
||||
this.elements.spinner.setAttribute('hidden', '');
|
||||
},
|
||||
|
||||
initMyProfile() {
|
||||
const accountButton = document.querySelector<
|
||||
HTMLElement & {
|
||||
onButtonTap: () => void;
|
||||
}
|
||||
>('ytmusic-settings-button');
|
||||
|
||||
accountButton?.onButtonTap();
|
||||
setTimeout(() => {
|
||||
accountButton?.onButtonTap();
|
||||
const renderer = document.querySelector<
|
||||
HTMLElement & { data: unknown }
|
||||
>('ytd-active-account-header-renderer');
|
||||
if (!accountButton || !renderer) {
|
||||
console.warn('Music Together: Cannot find account');
|
||||
this.me = getDefaultProfile(this.connection?.id ?? '');
|
||||
return;
|
||||
}
|
||||
|
||||
const accountData = renderer.data as RawAccountData;
|
||||
this.me = {
|
||||
handleId: accountData.channelHandle.runs[0].text,
|
||||
name: accountData.accountName.runs[0].text,
|
||||
thumbnail: accountData.accountPhoto.thumbnails[0].url,
|
||||
};
|
||||
|
||||
if (this.me.thumbnail) {
|
||||
this.popups.host.setProfile(this.me.thumbnail);
|
||||
this.popups.guest.setProfile(this.me.thumbnail);
|
||||
this.popups.setting.setProfile(this.me.thumbnail);
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
/* hooks */
|
||||
|
||||
start({ ipc }) {
|
||||
this.ipc = ipc;
|
||||
this.showPrompt = async (title: string, label: string) =>
|
||||
ipc.invoke('music-together:prompt', title, label) as Promise<string>;
|
||||
this.api = document.querySelector<AppElement>('ytmusic-app');
|
||||
|
||||
/* setup */
|
||||
document
|
||||
.querySelector('#right-content > ytmusic-settings-button')
|
||||
?.insertAdjacentHTML('beforebegin', settingHTML);
|
||||
const setting = document.querySelector<HTMLElement>(
|
||||
'#music-together-setting-button',
|
||||
);
|
||||
const icon = document.querySelector<SVGElement>(
|
||||
'#music-together-setting-button > svg',
|
||||
);
|
||||
const spinner = document.querySelector<HTMLElement>(
|
||||
'#music-together-setting-button > tp-yt-paper-spinner-lite',
|
||||
);
|
||||
if (!setting || !icon || !spinner) {
|
||||
console.warn('Music Together: Cannot inject html');
|
||||
console.log(setting, icon, spinner);
|
||||
return;
|
||||
}
|
||||
|
||||
this.elements = {
|
||||
setting,
|
||||
icon,
|
||||
spinner,
|
||||
};
|
||||
|
||||
this.stateInterval = window.setInterval(() => {
|
||||
if (this.connection?.mode !== 'host') return;
|
||||
const index = this.queue?.selectedIndex ?? 0;
|
||||
|
||||
this.connection.broadcast('SYNC_PROGRESS', {
|
||||
progress: this.playerApi?.getCurrentTime(),
|
||||
state: this.playerApi?.getPlayerState(),
|
||||
index,
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
/* UI */
|
||||
const hostPopup = createHostPopup({
|
||||
onItemClick: (id) => {
|
||||
if (id === 'music-together-close') {
|
||||
this.onStop();
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.closed'),
|
||||
);
|
||||
hostPopup.dismiss();
|
||||
}
|
||||
|
||||
if (id === 'music-together-copy-id') {
|
||||
navigator.clipboard
|
||||
.writeText(this.connection?.id ?? '')
|
||||
.then(() => {
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.id-copied'),
|
||||
);
|
||||
hostPopup.dismiss();
|
||||
})
|
||||
.catch(() => {
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.id-copy-failed'),
|
||||
);
|
||||
hostPopup.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
if (id === 'music-together-permission') {
|
||||
if (this.permission === 'all') this.permission = 'host-only';
|
||||
else if (this.permission === 'playlist') this.permission = 'all';
|
||||
else if (this.permission === 'host-only')
|
||||
this.permission = 'playlist';
|
||||
this.connection?.broadcast('PERMISSION', this.permission);
|
||||
|
||||
hostPopup.setPermission(this.permission);
|
||||
guestPopup.setPermission(this.permission);
|
||||
settingPopup.setPermission(this.permission);
|
||||
|
||||
const permissionLabel = t(
|
||||
`plugins.music-together.menu.permission.${this.permission}`,
|
||||
);
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.permission-changed', {
|
||||
permission: permissionLabel,
|
||||
}),
|
||||
);
|
||||
const item = hostPopup.items.find((it) => it?.element.id === id);
|
||||
if (item?.type === 'item') {
|
||||
item.setText(t('plugins.music-together.menu.set-permission'));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
const guestPopup = createGuestPopup({
|
||||
onItemClick: (id) => {
|
||||
if (id === 'music-together-disconnect') {
|
||||
this.onStop();
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.disconnected'),
|
||||
);
|
||||
guestPopup.dismiss();
|
||||
}
|
||||
},
|
||||
});
|
||||
const settingPopup = createSettingPopup({
|
||||
onItemClick: async (id) => {
|
||||
if (id === 'music-together-host') {
|
||||
settingPopup.dismiss();
|
||||
this.showSpinner();
|
||||
const result = await this.onHost();
|
||||
this.hideSpinner();
|
||||
|
||||
if (result) {
|
||||
navigator.clipboard
|
||||
.writeText(this.connection?.id ?? '')
|
||||
.then(() => {
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.id-copied'),
|
||||
);
|
||||
hostPopup.showAtAnchor(setting);
|
||||
})
|
||||
.catch(() => {
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.id-copy-failed'),
|
||||
);
|
||||
hostPopup.showAtAnchor(setting);
|
||||
});
|
||||
} else {
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.host-failed'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (id === 'music-together-join') {
|
||||
settingPopup.dismiss();
|
||||
this.showSpinner();
|
||||
const result = await this.onJoin();
|
||||
this.hideSpinner();
|
||||
|
||||
if (result) {
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.joined'),
|
||||
);
|
||||
guestPopup.showAtAnchor(setting);
|
||||
} else {
|
||||
this.api?.toastService?.show(
|
||||
t('plugins.music-together.toast.join-failed'),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
this.popups = {
|
||||
host: hostPopup,
|
||||
guest: guestPopup,
|
||||
setting: settingPopup,
|
||||
};
|
||||
setting.addEventListener('click', () => {
|
||||
let popup = settingPopup;
|
||||
if (this.connection?.mode === 'host') popup = hostPopup;
|
||||
if (this.connection?.mode === 'guest') popup = guestPopup;
|
||||
|
||||
if (popup.isShowing()) popup.dismiss();
|
||||
else popup.showAtAnchor(setting);
|
||||
});
|
||||
|
||||
/* account data getter */
|
||||
this.initMyProfile();
|
||||
},
|
||||
onPlayerApiReady(playerApi) {
|
||||
this.queue = new Queue({
|
||||
owner: {
|
||||
id: this.connection?.id ?? '',
|
||||
...this.me!,
|
||||
},
|
||||
getProfile: (id) => this.profiles[id],
|
||||
});
|
||||
this.playerApi = playerApi;
|
||||
|
||||
this.playerApi.addEventListener(
|
||||
'onStateChange',
|
||||
this.videoStateChangeListener,
|
||||
);
|
||||
document.addEventListener('videodatachange', this.videoChangeListener);
|
||||
},
|
||||
stop() {
|
||||
const dividers = Array.from(
|
||||
document.querySelectorAll('.music-together-divider'),
|
||||
);
|
||||
dividers.forEach((divider) => divider.remove());
|
||||
|
||||
this.elements.setting?.remove();
|
||||
this.onStop();
|
||||
if (typeof this.stateInterval === 'number')
|
||||
clearInterval(this.stateInterval);
|
||||
if (this.playerApi)
|
||||
this.playerApi.removeEventListener(
|
||||
'onStateChange',
|
||||
this.videoStateChangeListener,
|
||||
);
|
||||
if (this.videoChangeListener)
|
||||
document.removeEventListener(
|
||||
'videodatachange',
|
||||
this.videoChangeListener,
|
||||
);
|
||||
},
|
||||
start: onRendererLoad,
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
import { SHA1Hash } from './sha1hash';
|
||||
|
||||
export const extractToken = (cookie = document.cookie) =>
|
||||
cookie.match(/SAPISID=([^;]+);/)?.[1] ??
|
||||
cookie.match(/__Secure-3PAPISID=([^;]+);/)?.[1];
|
||||
|
||||
export const getHash = async (
|
||||
papisid: string,
|
||||
millis = Date.now(),
|
||||
origin: string = 'https://music.youtube.com',
|
||||
) => (await SHA1Hash(`${millis} ${papisid} ${origin}`)).toLowerCase();
|
||||
|
||||
export const getAuthorizationHeader = async (
|
||||
papisid: string,
|
||||
millis = Date.now(),
|
||||
origin: string = 'https://music.youtube.com',
|
||||
) => {
|
||||
return `SAPISIDHASH ${millis}_${await getHash(papisid, millis, origin)}`;
|
||||
};
|
||||
|
||||
export const getClient = () => {
|
||||
return {
|
||||
hl: navigator.language.split('-')[0] ?? 'en',
|
||||
gl: navigator.language.split('-')[1] ?? 'US',
|
||||
deviceMake: '',
|
||||
deviceModel: '',
|
||||
userAgent: navigator.userAgent,
|
||||
clientName: 'WEB_REMIX',
|
||||
clientVersion: '1.20231208.05.02',
|
||||
osName: '',
|
||||
osVersion: '',
|
||||
platform: 'DESKTOP',
|
||||
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
locationInfo: {
|
||||
locationPermissionAuthorizationStatus:
|
||||
'LOCATION_PERMISSION_AUTHORIZATION_STATUS_UNSUPPORTED',
|
||||
},
|
||||
musicAppInfo: {
|
||||
pwaInstallabilityStatus: 'PWA_INSTALLABILITY_STATUS_UNKNOWN',
|
||||
webDisplayMode: 'WEB_DISPLAY_MODE_BROWSER',
|
||||
storeDigitalGoodsApiSupportStatus: {
|
||||
playStoreDigitalGoodsApiSupportStatus:
|
||||
'DIGITAL_GOODS_API_SUPPORT_STATUS_UNSUPPORTED',
|
||||
},
|
||||
},
|
||||
utcOffsetMinutes: -1 * new Date().getTimezoneOffset(),
|
||||
};
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
export * from './queue';
|
||||
@ -1,529 +0,0 @@
|
||||
import { getMusicQueueRenderer } from './song';
|
||||
import { mapQueueItem } from './utils';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import type { ConnectionEventUnion } from '@/plugins/music-together/connection';
|
||||
import type { Profile, VideoData } from '../types';
|
||||
import type { QueueItem } from '@/types/datahost-get-state';
|
||||
import type { QueueElement } from '@/types/queue';
|
||||
|
||||
const getHeaderPayload = (() => {
|
||||
let payload: {
|
||||
items?: QueueItem[] | undefined;
|
||||
title: {
|
||||
runs: {
|
||||
text: string;
|
||||
}[];
|
||||
};
|
||||
subtitle: {
|
||||
runs: {
|
||||
text: string;
|
||||
}[];
|
||||
};
|
||||
buttons: {
|
||||
chipCloudChipRenderer: {
|
||||
style: {
|
||||
styleType: string;
|
||||
};
|
||||
text: {
|
||||
runs: {
|
||||
text: string;
|
||||
}[];
|
||||
};
|
||||
navigationEndpoint: {
|
||||
saveQueueToPlaylistCommand: unknown;
|
||||
};
|
||||
icon: {
|
||||
iconType: string;
|
||||
};
|
||||
accessibilityData: {
|
||||
accessibilityData: {
|
||||
label: string;
|
||||
};
|
||||
};
|
||||
isSelected: boolean;
|
||||
uniqueId: string;
|
||||
};
|
||||
}[];
|
||||
} | null = null;
|
||||
|
||||
return () => {
|
||||
if (!payload) {
|
||||
payload = {
|
||||
title: {
|
||||
runs: [
|
||||
{
|
||||
text: t('plugins.music-together.internal.track-source'),
|
||||
},
|
||||
],
|
||||
},
|
||||
subtitle: {
|
||||
runs: [
|
||||
{
|
||||
text: t('plugins.music-together.name'),
|
||||
},
|
||||
],
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
chipCloudChipRenderer: {
|
||||
style: {
|
||||
styleType: 'STYLE_TRANSPARENT',
|
||||
},
|
||||
text: {
|
||||
runs: [
|
||||
{
|
||||
text: t('plugins.music-together.internal.save'),
|
||||
},
|
||||
],
|
||||
},
|
||||
navigationEndpoint: {
|
||||
saveQueueToPlaylistCommand: {},
|
||||
},
|
||||
icon: {
|
||||
iconType: 'ADD_TO_PLAYLIST',
|
||||
},
|
||||
accessibilityData: {
|
||||
accessibilityData: {
|
||||
label: t('plugins.music-together.internal.save'),
|
||||
},
|
||||
},
|
||||
isSelected: false,
|
||||
uniqueId: t('plugins.music-together.internal.save'),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
})();
|
||||
|
||||
export type QueueOptions = {
|
||||
videoList?: VideoData[];
|
||||
owner?: Profile;
|
||||
queue?: QueueElement;
|
||||
getProfile: (id: string) => Profile | undefined;
|
||||
};
|
||||
export type QueueEventListener = (event: ConnectionEventUnion) => void;
|
||||
|
||||
export class Queue {
|
||||
private readonly queue: QueueElement;
|
||||
|
||||
private originalDispatch?: (obj: {
|
||||
type: string;
|
||||
payload?: { items?: QueueItem[] | undefined };
|
||||
}) => void;
|
||||
|
||||
private internalDispatch = false;
|
||||
private ignoreFlag = false;
|
||||
private listeners: QueueEventListener[] = [];
|
||||
|
||||
private owner: Profile | null;
|
||||
private readonly getProfile: (id: string) => Profile | undefined;
|
||||
|
||||
constructor(options: QueueOptions) {
|
||||
this.getProfile = options.getProfile;
|
||||
this.queue =
|
||||
options.queue ?? document.querySelector<QueueElement>('#queue')!;
|
||||
this.owner = options.owner ?? null;
|
||||
this._videoList = options.videoList ?? [];
|
||||
}
|
||||
|
||||
private _videoList: VideoData[] = [];
|
||||
|
||||
/* utils */
|
||||
get videoList() {
|
||||
return this._videoList;
|
||||
}
|
||||
|
||||
get selectedIndex() {
|
||||
return (
|
||||
mapQueueItem(
|
||||
(it) => it?.selected,
|
||||
this.queue.queue.store.store.getState().queue.items,
|
||||
).findIndex(Boolean) ?? 0
|
||||
);
|
||||
}
|
||||
|
||||
get rawItems() {
|
||||
return this.queue?.queue.store.store.getState().queue.items;
|
||||
}
|
||||
|
||||
get flatItems() {
|
||||
return mapQueueItem((it) => it, this.rawItems);
|
||||
}
|
||||
|
||||
setOwner(owner: Profile) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
/* public */
|
||||
async setVideoList(videoList: VideoData[], sync = true) {
|
||||
this._videoList = videoList;
|
||||
|
||||
if (sync) await this.syncVideo();
|
||||
}
|
||||
|
||||
async addVideos(videos: VideoData[], index?: number) {
|
||||
const response = await getMusicQueueRenderer(
|
||||
videos.map((it) => it.videoId),
|
||||
);
|
||||
if (!response) return false;
|
||||
|
||||
const items = response.queueDatas.map((it) => it?.content).filter(Boolean);
|
||||
if (!items) return false;
|
||||
|
||||
this.internalDispatch = true;
|
||||
this._videoList.push(...videos);
|
||||
this.queue?.dispatch({
|
||||
type: 'ADD_ITEMS',
|
||||
payload: {
|
||||
nextQueueItemId:
|
||||
this.queue.queue.store.store.getState().queue.nextQueueItemId,
|
||||
index:
|
||||
index ??
|
||||
this.queue.queue.store.store.getState().queue.items.length ??
|
||||
0,
|
||||
items,
|
||||
shuffleEnabled: false,
|
||||
shouldAssignIds: true,
|
||||
},
|
||||
});
|
||||
this.internalDispatch = false;
|
||||
setTimeout(() => {
|
||||
this.initQueue();
|
||||
this.syncQueueOwner();
|
||||
}, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
removeVideo(index: number) {
|
||||
this.internalDispatch = true;
|
||||
this._videoList.splice(index, 1);
|
||||
this.queue?.dispatch({
|
||||
type: 'REMOVE_ITEM',
|
||||
payload: index,
|
||||
});
|
||||
this.internalDispatch = false;
|
||||
setTimeout(() => {
|
||||
this.initQueue();
|
||||
this.syncQueueOwner();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
setIndex(index: number) {
|
||||
this.internalDispatch = true;
|
||||
this.queue?.dispatch({
|
||||
type: 'SET_INDEX',
|
||||
payload: index,
|
||||
});
|
||||
this.internalDispatch = false;
|
||||
}
|
||||
|
||||
moveItem(fromIndex: number, toIndex: number) {
|
||||
this.internalDispatch = true;
|
||||
const data = this._videoList.splice(fromIndex, 1)[0];
|
||||
this._videoList.splice(toIndex, 0, data);
|
||||
this.queue?.dispatch({
|
||||
type: 'MOVE_ITEM',
|
||||
payload: {
|
||||
fromIndex,
|
||||
toIndex,
|
||||
},
|
||||
});
|
||||
this.internalDispatch = false;
|
||||
setTimeout(() => {
|
||||
this.initQueue();
|
||||
this.syncQueueOwner();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.internalDispatch = true;
|
||||
this._videoList = [];
|
||||
this.queue?.dispatch({
|
||||
type: 'CLEAR',
|
||||
});
|
||||
this.internalDispatch = false;
|
||||
}
|
||||
|
||||
on(listener: QueueEventListener) {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
off(listener: QueueEventListener) {
|
||||
this.listeners = this.listeners.filter((it) => it !== listener);
|
||||
}
|
||||
|
||||
rollbackInjection() {
|
||||
if (!this.queue) {
|
||||
console.error('Queue is not initialized!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.originalDispatch)
|
||||
this.queue.queue.store.store.dispatch = this.originalDispatch;
|
||||
}
|
||||
|
||||
injection() {
|
||||
if (!this.queue) {
|
||||
console.error('Queue is not initialized!');
|
||||
return;
|
||||
}
|
||||
|
||||
this.originalDispatch = this.queue.queue.store.store.dispatch;
|
||||
this.queue.queue.store.store.dispatch = (event) => {
|
||||
if (!this.queue || !this.owner) {
|
||||
console.error('Queue is not initialized!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.internalDispatch) {
|
||||
if (event.type === 'CLEAR') {
|
||||
this.ignoreFlag = true;
|
||||
}
|
||||
if (event.type === 'ADD_ITEMS') {
|
||||
if (this.ignoreFlag) {
|
||||
this.ignoreFlag = false;
|
||||
const videoList = mapQueueItem(
|
||||
(it) =>
|
||||
({
|
||||
videoId: it!.videoId,
|
||||
ownerId: this.owner!.id,
|
||||
}) satisfies VideoData,
|
||||
event.payload!.items!,
|
||||
);
|
||||
const index = this._videoList.length + videoList.length - 1;
|
||||
|
||||
if (videoList.length > 0) {
|
||||
this.broadcast({
|
||||
// play
|
||||
type: 'ADD_SONGS',
|
||||
payload: {
|
||||
videoList,
|
||||
},
|
||||
after: [
|
||||
{
|
||||
type: 'SYNC_PROGRESS',
|
||||
payload: {
|
||||
index,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
} else if (
|
||||
(
|
||||
event.payload as {
|
||||
items: unknown[];
|
||||
}
|
||||
).items.length === 1
|
||||
) {
|
||||
this.broadcast({
|
||||
// add playlist
|
||||
type: 'ADD_SONGS',
|
||||
payload: {
|
||||
// index: (event.payload as any).index,
|
||||
videoList: mapQueueItem(
|
||||
(it) =>
|
||||
({
|
||||
videoId: it!.videoId,
|
||||
ownerId: this.owner!.id,
|
||||
}) satisfies VideoData,
|
||||
event.payload!.items!,
|
||||
),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type === 'MOVE_ITEM') {
|
||||
this.broadcast({
|
||||
type: 'MOVE_SONG',
|
||||
payload: {
|
||||
fromIndex: (
|
||||
event.payload as {
|
||||
fromIndex: number;
|
||||
}
|
||||
).fromIndex,
|
||||
toIndex: (
|
||||
event.payload as {
|
||||
toIndex: number;
|
||||
}
|
||||
).toIndex,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (event.type === 'REMOVE_ITEM') {
|
||||
this.broadcast({
|
||||
type: 'REMOVE_SONG',
|
||||
payload: {
|
||||
index: event.payload as number,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (event.type === 'SET_INDEX') {
|
||||
this.broadcast({
|
||||
type: 'SYNC_PROGRESS',
|
||||
payload: {
|
||||
index: event.payload as number,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type === 'SET_HEADER') event.payload = getHeaderPayload();
|
||||
if (event.type === 'ADD_STEERING_CHIPS') {
|
||||
event.type = 'CLEAR_STEERING_CHIPS';
|
||||
event.payload = undefined;
|
||||
}
|
||||
if (event.type === 'SET_PLAYER_UI_STATE') {
|
||||
if (
|
||||
(event.payload as string) === 'INACTIVE' &&
|
||||
this.videoList.length > 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (event.type === 'HAS_SHOWN_AUTOPLAY') return;
|
||||
if (event.type === 'ADD_AUTOMIX_ITEMS') return;
|
||||
}
|
||||
|
||||
const fakeContext = {
|
||||
...this.queue,
|
||||
queue: {
|
||||
...this.queue.queue,
|
||||
store: {
|
||||
...this.queue.queue.store,
|
||||
dispatch: this.originalDispatch,
|
||||
},
|
||||
},
|
||||
};
|
||||
this.originalDispatch?.call(fakeContext, event);
|
||||
};
|
||||
}
|
||||
|
||||
/* sync */
|
||||
initQueue() {
|
||||
if (!this.queue) return;
|
||||
|
||||
this.internalDispatch = true;
|
||||
this.queue.dispatch({
|
||||
type: 'HAS_SHOWN_AUTOPLAY',
|
||||
payload: false,
|
||||
});
|
||||
this.queue.dispatch({
|
||||
type: 'SET_HEADER',
|
||||
payload: getHeaderPayload(),
|
||||
});
|
||||
this.queue.dispatch({
|
||||
type: 'CLEAR_STEERING_CHIPS',
|
||||
});
|
||||
this.internalDispatch = false;
|
||||
}
|
||||
|
||||
async syncVideo() {
|
||||
const response = await getMusicQueueRenderer(
|
||||
this._videoList.map((it) => it.videoId),
|
||||
);
|
||||
if (!response) return false;
|
||||
|
||||
const items = response.queueDatas.map((it) => it.content);
|
||||
|
||||
this.internalDispatch = true;
|
||||
this.queue?.dispatch({
|
||||
type: 'UPDATE_ITEMS',
|
||||
payload: {
|
||||
items: items,
|
||||
nextQueueItemId:
|
||||
this.queue.queue.store.store.getState().queue.nextQueueItemId,
|
||||
shouldAssignIds: true,
|
||||
currentIndex: -1,
|
||||
},
|
||||
});
|
||||
this.internalDispatch = false;
|
||||
setTimeout(() => {
|
||||
this.initQueue();
|
||||
this.syncQueueOwner();
|
||||
}, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
syncQueueOwner() {
|
||||
const allQueue = document.querySelectorAll('#queue');
|
||||
|
||||
allQueue.forEach((queue) => {
|
||||
const list = Array.from(
|
||||
queue?.querySelectorAll<HTMLElement>('ytmusic-player-queue-item') ?? [],
|
||||
);
|
||||
|
||||
list.forEach((item, index: number | undefined) => {
|
||||
if (typeof index !== 'number') return;
|
||||
|
||||
const id = this._videoList[index]?.ownerId;
|
||||
const data = this.getProfile(id);
|
||||
|
||||
const profile =
|
||||
item.querySelector<HTMLImageElement>('.music-together-owner') ??
|
||||
document.createElement('img');
|
||||
profile.classList.add('music-together-owner');
|
||||
profile.dataset.id = id;
|
||||
profile.dataset.index = index.toString();
|
||||
|
||||
const name =
|
||||
item.querySelector<HTMLElement>('.music-together-name') ??
|
||||
document.createElement('div');
|
||||
name.classList.add('music-together-name');
|
||||
name.textContent =
|
||||
data?.name ?? t('plugins.music-together.internal.unknown-user');
|
||||
|
||||
if (data) {
|
||||
profile.dataset.thumbnail = data.thumbnail ?? '';
|
||||
profile.dataset.name = data.name ?? '';
|
||||
profile.dataset.handleId = data.handleId ?? '';
|
||||
profile.dataset.id = data.id ?? '';
|
||||
|
||||
profile.src = data.thumbnail ?? '';
|
||||
profile.title = data.name ?? '';
|
||||
profile.alt = data.handleId ?? '';
|
||||
}
|
||||
|
||||
if (!profile.isConnected) item.append(profile);
|
||||
if (!name.isConnected) item.append(name);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeQueueOwner() {
|
||||
const allQueue = document.querySelectorAll('#queue');
|
||||
|
||||
allQueue.forEach((queue) => {
|
||||
const list = Array.from(
|
||||
queue?.querySelectorAll<HTMLElement>('ytmusic-player-queue-item') ?? [],
|
||||
);
|
||||
|
||||
list.forEach((item) => {
|
||||
const profile = item.querySelector<HTMLImageElement>(
|
||||
'.music-together-owner',
|
||||
);
|
||||
const name = item.querySelector<HTMLElement>('.music-together-name');
|
||||
profile?.remove();
|
||||
name?.remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* private */
|
||||
private broadcast(event: ConnectionEventUnion) {
|
||||
this.listeners.forEach((listener) => listener(event));
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
export const SHA1Hash = async (str: string) => {
|
||||
const enc = new TextEncoder();
|
||||
const hash = await crypto.subtle.digest('SHA-1', enc.encode(str));
|
||||
return Array.from(new Uint8Array(hash))
|
||||
.map((v) => v.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
};
|
||||