mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 10:31:47 +00:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34cb79eeaf | |||
| 1c30a07031 | |||
| 5dd5f41ef5 | |||
| 45a3c11d51 | |||
| 8bd3b4d3f0 | |||
| 4e3cb5806d | |||
| 6563eb4ddd | |||
| 895386f6f8 | |||
| 3810955e56 | |||
| 59c521e53f | |||
| 25d266f8f9 | |||
| 0c3c380591 | |||
| a20cfa30a1 | |||
| fefe899393 | |||
| 55759e8d7a | |||
| ddb561937f | |||
| 198cb71a4c | |||
| c34b880752 | |||
| 76944e3e41 | |||
| 68cd76f2af | |||
| 81145b52b7 | |||
| 2a19dab061 | |||
| 6958d59d4f | |||
| 8a51dfad87 | |||
| 5bb4d9efbe | |||
| 927aa5f24b | |||
| d695bc93a1 | |||
| b05fb4ccbe | |||
| 299eb7e7d6 | |||
| ae26333224 | |||
| 35176469b0 | |||
| 4e74f9cbc5 | |||
| 4091b36f36 | |||
| b3f805fce6 | |||
| b129a3e8d8 | |||
| 64ea1fdb58 | |||
| 8fcf59ed0a | |||
| 9811ca63de | |||
| 9028f88299 | |||
| fd47766d93 | |||
| 26b12c7208 | |||
| 8da9b3454d | |||
| 205cbefc83 |
BIN
assets/youtube-music-tray-paused.png
Normal file
BIN
assets/youtube-music-tray-paused.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
10
changelog.md
10
changelog.md
@ -2,8 +2,18 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
|
||||
|
||||
#### [v3.2.1](https://github.com/th-ch/youtube-music/compare/v3.2.0...v3.2.1)
|
||||
|
||||
- fix: fix #1574 [`#1574`](https://github.com/th-ch/youtube-music/issues/1574)
|
||||
- fix: fix #1575 [`#1575`](https://github.com/th-ch/youtube-music/issues/1575)
|
||||
- chore(i18n): Translated using Weblate [`f5aa179`](https://github.com/th-ch/youtube-music/commit/f5aa179cd639eb4b8f70f1264b5b459ebcc16695)
|
||||
- chore(i18n): Translated using Weblate (English) [`e409165`](https://github.com/th-ch/youtube-music/commit/e409165e1bed85f3d1aea3a565e7b9e462b1e05b)
|
||||
- chore(i18n): Translated using Weblate (Czech) [`0ca4e34`](https://github.com/th-ch/youtube-music/commit/0ca4e34efd86e877314e5a245f266065b4cf0013)
|
||||
|
||||
#### [v3.2.0](https://github.com/th-ch/youtube-music/compare/v3.1.1...v3.2.0)
|
||||
|
||||
> 1 January 2024
|
||||
|
||||
- feat(album-color-theme): improve `Album Color Theme` style [`#1571`](https://github.com/th-ch/youtube-music/pull/1571)
|
||||
- feat(menu): add more detail in Menu [`#1570`](https://github.com/th-ch/youtube-music/pull/1570)
|
||||
- feat(music-together): Add new plugin `Music Together` [`#1562`](https://github.com/th-ch/youtube-music/pull/1562)
|
||||
|
||||
29
package.json
29
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "youtube-music",
|
||||
"productName": "YouTube Music",
|
||||
"version": "3.2.1",
|
||||
"version": "3.2.2",
|
||||
"description": "YouTube Music Desktop App - including custom plugins",
|
||||
"main": "./dist/main/index.js",
|
||||
"license": "MIT",
|
||||
@ -123,14 +123,17 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"esbuild": "0.18.20",
|
||||
"esbuild": "0.19.11",
|
||||
"usocket": "1.0.1",
|
||||
"rollup": "4.9.2",
|
||||
"rollup": "4.9.3",
|
||||
"node-gyp": "10.0.1",
|
||||
"xml2js": "0.6.2",
|
||||
"node-fetch": "3.3.2",
|
||||
"@electron/universal": "2.0.1",
|
||||
"@babel/runtime": "7.23.7"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"vudio@2.1.1": "patches/vudio@2.1.1.patch"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@ -163,7 +166,7 @@
|
||||
"filenamify": "6.0.0",
|
||||
"howler": "2.2.4",
|
||||
"html-to-text": "9.0.5",
|
||||
"i18next": "23.7.13",
|
||||
"i18next": "23.7.16",
|
||||
"keyboardevent-from-electron-accelerator": "2.0.0",
|
||||
"keyboardevents-areequal": "0.2.2",
|
||||
"node-html-parser": "6.1.12",
|
||||
@ -178,23 +181,23 @@
|
||||
"youtubei.js": "8.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.41.0-alpha-dec-18-2023",
|
||||
"@playwright/test": "1.41.0-alpha-jan-5-2024",
|
||||
"@total-typescript/ts-reset": "0.5.1",
|
||||
"@types/color": "3.0.6",
|
||||
"@types/electron-localshortcut": "3.1.3",
|
||||
"@types/howler": "2.2.11",
|
||||
"@types/html-to-text": "9.0.4",
|
||||
"@types/semver": "7.5.6",
|
||||
"@typescript-eslint/eslint-plugin": "6.16.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.17.0",
|
||||
"bufferutil": "4.0.8",
|
||||
"builtin-modules": "3.3.0",
|
||||
"cross-env": "7.0.3",
|
||||
"del-cli": "5.1.0",
|
||||
"electron": "28.1.0",
|
||||
"electron": "29.0.0-alpha.7",
|
||||
"electron-builder": "24.9.1",
|
||||
"electron-devtools-installer": "3.2.0",
|
||||
"electron-vite": "2.0.0-beta.2",
|
||||
"esbuild": "0.18.20",
|
||||
"electron-vite": "2.0.0-beta.3",
|
||||
"esbuild": "0.19.11",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-import-resolver-exports": "1.0.0-beta.5",
|
||||
"eslint-import-resolver-typescript": "3.6.1",
|
||||
@ -202,11 +205,11 @@
|
||||
"eslint-plugin-prettier": "5.1.2",
|
||||
"glob": "10.3.10",
|
||||
"node-gyp": "10.0.1",
|
||||
"playwright": "1.41.0-alpha-dec-18-2023",
|
||||
"rollup": "4.9.2",
|
||||
"playwright": "1.41.0-alpha-jan-5-2024",
|
||||
"rollup": "4.9.3",
|
||||
"typescript": "5.3.3",
|
||||
"utf-8-validate": "6.0.3",
|
||||
"vite": "5.0.10",
|
||||
"vite": "5.0.11",
|
||||
"vite-plugin-inspect": "0.8.1",
|
||||
"vite-plugin-resolve": "2.5.1",
|
||||
"ws": "8.16.0"
|
||||
@ -217,5 +220,5 @@
|
||||
"unreleased": true,
|
||||
"output": "changelog.md"
|
||||
},
|
||||
"packageManager": "pnpm@8.13.1"
|
||||
"packageManager": "pnpm@8.14.0"
|
||||
}
|
||||
|
||||
20
patches/vudio@2.1.1.patch
Normal file
20
patches/vudio@2.1.1.patch
Normal file
@ -0,0 +1,20 @@
|
||||
diff --git a/umd/vudio.js b/umd/vudio.js
|
||||
index d0d1127e57125ad4e77442af2db4a26998c7b385..c0b66bd4327c65c31dc6e588bfa4ae6ec70bd3b8 100644
|
||||
--- a/umd/vudio.js
|
||||
+++ b/umd/vudio.js
|
||||
@@ -147,7 +147,6 @@
|
||||
|
||||
source.connect(this.analyser);
|
||||
this.analyser.fftSize = this.option.accuracy * 2;
|
||||
- this.analyser.connect(audioContext.destination);
|
||||
|
||||
this.freqByteData = new Uint8Array(this.analyser.frequencyBinCount);
|
||||
|
||||
@@ -207,7 +206,6 @@
|
||||
|
||||
source.connect(this.analyser);
|
||||
this.analyser.fftSize = this.option.accuracy * 2;
|
||||
- this.analyser.connect(audioContext.destination);
|
||||
},
|
||||
|
||||
__rebuildData : function (freqByteData, horizontalAlign) {
|
||||
746
pnpm-lock.yaml
generated
746
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -293,7 +293,7 @@
|
||||
"menu": {
|
||||
"advanced": "Pokročilý"
|
||||
},
|
||||
"name": "Prolínání [beta]",
|
||||
"name": "Prolínání [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -413,7 +413,7 @@
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Přidává Lumia Stream podporu",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Přidává lyrics podporu pro většinu písniček",
|
||||
@ -422,6 +422,7 @@
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"description": "Sdílejte seznam písniček s ostatními. Když the host hraje písničku, uslyší jí i všichni ostatní.",
|
||||
"dialog": {
|
||||
"enter-host": "Zadejte Host ID"
|
||||
},
|
||||
@ -430,6 +431,7 @@
|
||||
"unknown-user": "Neznámý uživatel"
|
||||
},
|
||||
"menu": {
|
||||
"click-to-copy-id": "Zkopírovat Host ID",
|
||||
"close": "Zavřít Hudba Spolu",
|
||||
"connected-users": "Připojení uživatelé",
|
||||
"disconnect": "Odpojit od Hudby Spolu",
|
||||
@ -437,9 +439,11 @@
|
||||
"host": "Hudba Spolu Host",
|
||||
"join": "Připojit se k Hudbě Spolu",
|
||||
"permission": {
|
||||
"playlist": "Seznam písniček Control"
|
||||
"all": "Povolit hostům ovládat seznam písniček a přehrávač",
|
||||
"host-only": "Jenom host může ovládat seznam písniček a přehrávač",
|
||||
"playlist": "Povolit hostům ovládat seznam písniček"
|
||||
},
|
||||
"set-permission": "Změnit Control oprávnění",
|
||||
"set-permission": "Změnit ovládací oprávnění",
|
||||
"status": {
|
||||
"disconnected": "Odpojen",
|
||||
"guest": "Připojený/á jako Guest",
|
||||
@ -583,7 +587,7 @@
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Ovládejte přehrávání z vašeho hlavního panelu Windows",
|
||||
"description": "Ovládejte přehrávání z vašeho Windows hlavního panelu",
|
||||
"name": "Hlavní panel Media Control"
|
||||
},
|
||||
"touchbar": {
|
||||
|
||||
@ -186,6 +186,10 @@
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
},
|
||||
"next": "Next",
|
||||
"play-pause": "Play/Pause",
|
||||
"previous": "Previous",
|
||||
@ -294,7 +298,7 @@
|
||||
"menu": {
|
||||
"advanced": "Advanced"
|
||||
},
|
||||
"name": "Crossfade [beta]",
|
||||
"name": "Crossfade [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -419,7 +423,7 @@
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Adds Lumia Stream support",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Adds lyrics support for most songs",
|
||||
@ -468,6 +472,7 @@
|
||||
"disconnected": "Music Together disconnected",
|
||||
"host-failed": "Failed to host Music Together",
|
||||
"id-copied": "Host ID copied to clipboard",
|
||||
"id-copy-failed": "Failed to copy Host ID to clipboard",
|
||||
"join-failed": "Failed to join Music Together",
|
||||
"joined": "Joined Music Together",
|
||||
"permission-changed": "Music Together permission changed to \"{{permission}}\"",
|
||||
|
||||
@ -191,7 +191,11 @@
|
||||
"previous": "Anterior",
|
||||
"quit": "Salir",
|
||||
"restart": "Reiniciar la aplicación",
|
||||
"show": "Mostrar ventana"
|
||||
"show": "Mostrar ventana",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -294,7 +298,7 @@
|
||||
"menu": {
|
||||
"advanced": "Avanzado"
|
||||
},
|
||||
"name": "Crossfade [beta]",
|
||||
"name": "Crossfade [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -419,7 +423,7 @@
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Agrega soporte para Lumia Stream",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Añade el soporte para las letras para la mayoría de las canciones",
|
||||
@ -450,9 +454,9 @@
|
||||
"host": "Host de Music Together",
|
||||
"join": "Únase a Music Together",
|
||||
"permission": {
|
||||
"all": "Todo el control",
|
||||
"host-only": "Solo anfitrión",
|
||||
"playlist": "Control de las listas de reproducción"
|
||||
"all": "Permite a los invitados controlar la lista de reproducción y el reproductor",
|
||||
"host-only": "Sólo el anfitrión puede controlar la lista de reproducción y el reproductor",
|
||||
"playlist": "Permita que los invitados controlen la lista de reproducción"
|
||||
},
|
||||
"set-permission": "Permiso de control de cambios",
|
||||
"status": {
|
||||
|
||||
@ -289,7 +289,7 @@
|
||||
"menu": {
|
||||
"advanced": "Avancé"
|
||||
},
|
||||
"name": "Fondu enchaîné [bêta]",
|
||||
"name": "Fondu enchaîné [Bêta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -414,7 +414,7 @@
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Ajoute la prise en charge de Lumia Stream",
|
||||
"name": "Lumia Stream [bêta]"
|
||||
"name": "Lumia Stream [Bêta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Ajoute la prise en charge des paroles pour la plupart des chansons",
|
||||
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "Attivato",
|
||||
"label": "Plugin"
|
||||
"label": "Plugin",
|
||||
"new": "NUOVO"
|
||||
},
|
||||
"view": {
|
||||
"label": "Visualizzazione",
|
||||
@ -201,6 +202,10 @@
|
||||
},
|
||||
"name": "Adblocker"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Aggiunge i pulsanti Undislike, Dislike, Like e Unlike a tutti i brani di una playlist o di un album.",
|
||||
"name": "Azioni album"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "Applica un tema dinamico e degli effetti visivi basandosi sul colore dell'album",
|
||||
"name": "Tema abbinato a colore album"
|
||||
@ -414,7 +419,7 @@
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Aggiungi supporto per Lumia Stream",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Aggiunge il supporto dei testi per la maggior parte delle canzoni",
|
||||
@ -426,6 +431,51 @@
|
||||
"fetched-lyrics": "Testi recuperati per Genius"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"description": "Condividi una playlist con altri. Quando l'Host riproduce un brano, tutti gli altri ascolteranno lo stesso brano",
|
||||
"dialog": {
|
||||
"enter-host": "Inserisci l'ID dell'Host"
|
||||
},
|
||||
"internal": {
|
||||
"save": "Salva",
|
||||
"track-source": "Traccia sorgente",
|
||||
"unknown-user": "Utente sconosciuto"
|
||||
},
|
||||
"menu": {
|
||||
"click-to-copy-id": "Copia l'ID dell'Host",
|
||||
"close": "Chiudi Music Together",
|
||||
"connected-users": "Utenti connessi",
|
||||
"disconnect": "Disconetti Music Together",
|
||||
"empty-user": "Utenti non connessi",
|
||||
"host": "Music Together Host",
|
||||
"join": "Unisciti a Music Together",
|
||||
"permission": {
|
||||
"all": "Consenti ai Guest di controllare la playlist e il player",
|
||||
"host-only": "Solo l'Host può controllare la playlist e il player",
|
||||
"playlist": "Consenti ai Guest di controllare la playlist"
|
||||
},
|
||||
"set-permission": "Cambia autorizzazione di controllo",
|
||||
"status": {
|
||||
"disconnected": "Disconnesso",
|
||||
"guest": "Connesso come Guest",
|
||||
"host": "Connesso come Host"
|
||||
}
|
||||
},
|
||||
"name": "Music Together [Beta]",
|
||||
"toast": {
|
||||
"add-song-failed": "Impossibile aggiungere il brano",
|
||||
"closed": "Music Together chiuso",
|
||||
"disconnected": "Music Together disconnesso",
|
||||
"host-failed": "Impossibile ospitare Music Together",
|
||||
"id-copied": "L'ID dell Host è stato copiato negli appunti",
|
||||
"join-failed": "Impossibile unirsi a Music Together",
|
||||
"joined": "Unito a Music Together",
|
||||
"permission-changed": "L'autorizzazione di Music Together è cambiata in {{permission}}",
|
||||
"remove-song-failed": "Impossibile rimuovere il brano",
|
||||
"user-connected": "{{name}} si è unito a Music Together",
|
||||
"user-disconnected": "{{name}} ha lasciato Music Together"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"description": "Frecce di navigazione Avanti/Indietro integrate direttamente nell'interfaccia, come nel tuo browser preferito",
|
||||
"name": "Navigazione"
|
||||
|
||||
@ -191,7 +191,11 @@
|
||||
"previous": "이전",
|
||||
"quit": "종료",
|
||||
"restart": "앱 재시작",
|
||||
"show": "창 표시"
|
||||
"show": "창 표시",
|
||||
"tooltip": {
|
||||
"default": "유튜브 뮤직",
|
||||
"with-song-info": "유튜브 뮤직: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
@ -468,6 +472,7 @@
|
||||
"disconnected": "Music Together 연결이 끊어졌습니다",
|
||||
"host-failed": "Music Together를 열 수 없습니다",
|
||||
"id-copied": "호스트 아이디가 클립보드에 복사되었습니다",
|
||||
"id-copy-failed": "호스트 ID를 클립보드에 복사하지 못했습니다",
|
||||
"join-failed": "Music Together에 참여할 수 없습니다",
|
||||
"joined": "Music Together에 참여했습니다",
|
||||
"permission-changed": "Music Together 제어 권한이 \"{{permission}}\"(으)로 변경되었습니다",
|
||||
|
||||
@ -289,7 +289,7 @@
|
||||
"menu": {
|
||||
"advanced": "Išplėstinė"
|
||||
},
|
||||
"name": "Perliejimas [beta]",
|
||||
"name": "Perliejimas [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -414,7 +414,7 @@
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Prideda \"Lumia Stream\" palaikymą",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Prideda daugumai dainių žodžių tekstus",
|
||||
|
||||
@ -287,7 +287,7 @@
|
||||
"menu": {
|
||||
"advanced": "Avansert"
|
||||
},
|
||||
"name": "Overgang [beta]",
|
||||
"name": "Overgang [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -412,7 +412,7 @@
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Legger til Lumia Stream-støtte",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Gir sangtekststøtte for de fleste spor",
|
||||
|
||||
@ -414,7 +414,7 @@
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Dodaje obsługę Lumia Stream",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Dodaje obsługę tekstów dla większości piosenek",
|
||||
|
||||
@ -289,7 +289,7 @@
|
||||
"menu": {
|
||||
"advanced": "Avançado"
|
||||
},
|
||||
"name": "Transição entre músicas [beta]",
|
||||
"name": "Transição entre músicas [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -414,7 +414,7 @@
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Adiciona suporte Lumia Stream",
|
||||
"name": "Lumia Stream [beta]"
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Adiciona suporte a letras para a maioria das músicas",
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
"load-all": "Tüm eklentiler yükleniyor",
|
||||
"load-failed": "\"{{pluginName}}\" eklentisi yüklenemedi",
|
||||
"loaded": "\"{{pluginName}}\" eklentisi yüklendi",
|
||||
"unload-failed": "\"{{pluginName}}\" eklentisi kaldırılamadı.",
|
||||
"unload-failed": "\"{{pluginName}}\" eklentisi çıkartılamadı",
|
||||
"unloaded": "\"{{pluginName}}\" eklentisi kaldırıldı"
|
||||
}
|
||||
}
|
||||
@ -50,53 +50,117 @@
|
||||
},
|
||||
"need-to-restart": {
|
||||
"buttons": {
|
||||
"later": "Daha Sonra",
|
||||
"restart-now": "Şimdi yeniden başlat"
|
||||
}
|
||||
},
|
||||
"detail": "\"{{pluginName}}\" eklentisinin çalışabilmesi için yeniden başlatman gerekiyor",
|
||||
"message": "\"{{pluginName}}\" için yeniden başlatman gerekiyor",
|
||||
"title": "Uygulamayı yeniden başlatman gerekiyor"
|
||||
},
|
||||
"unresponsive": {
|
||||
"buttons": {
|
||||
"quit": "Çıkış",
|
||||
"relaunch": "Yeniden Başlat",
|
||||
"wait": "Bekle"
|
||||
},
|
||||
"detail": "Rahatsızlık için özür dileriz! Lütfen ne yapacağınızı seçin:",
|
||||
"message": "Uygulama yanıt vermiyor",
|
||||
"title": "Pencere yanıt vermiyor"
|
||||
},
|
||||
"update-available": {
|
||||
"buttons": {
|
||||
"disable": "Güncellemeleri devre dışı bırak",
|
||||
"download": "İndir",
|
||||
"ok": "Tamam"
|
||||
}
|
||||
},
|
||||
"detail": "Yeni bir sürüm mevcut. {{downloadLink}} adresi üzerinden indirebilirsin",
|
||||
"message": "Yeni bir sürüm mevcut",
|
||||
"title": "Güncelleme Mevcut"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"about": "Hakkında",
|
||||
"navigation": {
|
||||
"label": "Navigasyon"
|
||||
"label": "Navigasyon",
|
||||
"submenu": {
|
||||
"copy-current-url": "Geçerli Url'yi kopyala",
|
||||
"go-back": "Geri dön",
|
||||
"go-forward": "İlerle",
|
||||
"quit": "Çıkış",
|
||||
"restart": "Uygulamayı Yeniden Başlat"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"label": "Seçenekler",
|
||||
"submenu": {
|
||||
"advanced-options": {
|
||||
"label": "Gelişmiş Seçenekler",
|
||||
"submenu": {
|
||||
"auto-reset-app-cache": "Uygulama başlatıldığında uygulama önbelleğini sıfırla",
|
||||
"disable-hardware-acceleration": "Donanım hızlandırmayı devre dışı bırak",
|
||||
"edit-config-json": "Düzenle config.json",
|
||||
"override-user-agent": "\"User-Agent \"ı geçersiz kıl",
|
||||
"restart-on-config-changes": "Yapılandırma değişikliğinde yeniden başlat",
|
||||
"set-proxy": {
|
||||
"label": "Proxy ayarla",
|
||||
"prompt": {
|
||||
"label": "Proxy Adresini Gir: (devre dışı bırakmak için boş bırakın)",
|
||||
"placeholder": "Örnek: SOCKS5://127.0.0.1:9999",
|
||||
"title": "Proxy ayarla"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toggle-dev-tools": "DevTools'u Aç / Kapat"
|
||||
}
|
||||
},
|
||||
"always-on-top": "Her zaman üstte",
|
||||
"auto-update": "Otomatik Güncelleme",
|
||||
"language": {
|
||||
"label": "Dil"
|
||||
"hide-menu": {
|
||||
"dialog": {
|
||||
"message": "Menü bir sonraki açılışta gizlenecektir, göstermek için [Alt] tuşunu kullanın (veya uygulama-içi-menü kullanıyorsanız [`] tuşuna geri basın)",
|
||||
"title": "Gizli Menü Aktif"
|
||||
},
|
||||
"label": "Gizli Menü"
|
||||
},
|
||||
"language": {
|
||||
"dialog": {
|
||||
"message": "Dil değişikliği yeniden başlattıktan sonra etkinleşecektir",
|
||||
"title": "Dil değiştirildi"
|
||||
},
|
||||
"label": "Dil",
|
||||
"submenu": {
|
||||
"to-help-translate": "Çeviriye yardım etmek ister misiniz? Buraya tıklayın"
|
||||
}
|
||||
},
|
||||
"resume-on-start": "Uygulama başlatıldığında son şarkıyı devam ettir",
|
||||
"single-instance-lock": "Tek Örnek Kilidi",
|
||||
"start-at-login": "Başlangıçta çalıştır",
|
||||
"starting-page": {
|
||||
"label": "Başlangıç sayfası",
|
||||
"unset": "Ayarlanmadı"
|
||||
},
|
||||
"tray": {
|
||||
"label": "Tepsi",
|
||||
"submenu": {
|
||||
"enabled-and-hide-app": "Uygulamayı etkinleştirin gizleyin.",
|
||||
"play-pause-on-click": "Tıklaynınca Oynat-Duraklat"
|
||||
"disabled": "Devre Dışı",
|
||||
"enabled-and-hide-app": "Uygulamayı etkinleştirin gizleyin",
|
||||
"enabled-and-show-app": "Etkinleştir ve uygulamayı göster",
|
||||
"play-pause-on-click": "Tıklandığında Oynat/Duraklat"
|
||||
}
|
||||
},
|
||||
"visual-tweaks": {
|
||||
"label": "Görsel İnce Ayarlar",
|
||||
"submenu": {
|
||||
"like-buttons": {
|
||||
"default": "Varsayılan"
|
||||
"default": "Varsayılan",
|
||||
"force-show": "Zorla göster",
|
||||
"hide": "Gizle",
|
||||
"label": "Beğenme düğmeleri"
|
||||
},
|
||||
"remove-upgrade-button": "Yükseltme düğmesini kaldır",
|
||||
"theme": {
|
||||
"label": "Tema",
|
||||
"submenu": {
|
||||
"import-css-file": "Özel CSS dosyanı içeri aktar",
|
||||
"no-theme": "Tema Yok"
|
||||
}
|
||||
}
|
||||
@ -105,31 +169,54 @@
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"label": "Eklentiler"
|
||||
"enabled": "Aktif",
|
||||
"label": "Eklentiler",
|
||||
"new": "YENİ"
|
||||
},
|
||||
"view": {
|
||||
"label": "Görüntü"
|
||||
"label": "Görüntü",
|
||||
"submenu": {
|
||||
"force-reload": "Zorla yeniden başlat",
|
||||
"reload": "Yeniden Başlat",
|
||||
"reset-zoom": "Asıl Boyut",
|
||||
"toggle-fullscreen": "Tam Ekran'a Geçiş",
|
||||
"zoom-in": "Yakınlaştır",
|
||||
"zoom-out": "Uzaklaştır"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"next": "Sonraki",
|
||||
"play-pause": "Oynat/Durdur",
|
||||
"previous": "Önceki",
|
||||
"quit": "Çıkış",
|
||||
"restart": "Yeniden başlat",
|
||||
"show": "Pencereyi görüntüle"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"adblocker": {
|
||||
"description": "Tüm reklamları ve izleyicileri engelle",
|
||||
"menu": {
|
||||
"blocker": "Engelleme Yöntemi"
|
||||
"blocker": "Engelleyici"
|
||||
},
|
||||
"name": "Adblocker"
|
||||
"name": "Reklam Engelleyici"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "Çalma listesindeki veya albümdeki tüm şarkılara Beğendim ve Beğenmedim düğmeleri ekler.",
|
||||
"name": "Albüm Eylemleri"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "Albümün renk paletine dayalı dinamik bir tema ve efektler uygular.",
|
||||
"description": "Albümün renk paletine dayalı dinamik bir tema ve efektler uygular",
|
||||
"name": "Albüm Renk Teması"
|
||||
},
|
||||
"ambient-mode": {
|
||||
"description": "Videodaki yumuşak renkleri ekranınızın arka planına yansıtarak bir ışık efekti uygular..",
|
||||
"menu": {
|
||||
"blur-amount": {
|
||||
"label": "Bulanıklık miktarı",
|
||||
"submenu": {
|
||||
"pixels": "{{blurAmount}} pixels"
|
||||
"pixels": "{{blurAmount}} piksel"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
@ -145,53 +232,82 @@
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "Kalite",
|
||||
"submenu": {
|
||||
"pixels": "{{quality}} pixels"
|
||||
"pixels": "{{quality}} piksel"
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"label": "Boyut",
|
||||
"submenu": {
|
||||
"percent": "{{size}}%"
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"label": "Yumuşak Geçiş",
|
||||
"submenu": {
|
||||
"during": "{{interpolationTime}} saniye içinde"
|
||||
"during": "{{interpolationTime}} saniye boyunca"
|
||||
}
|
||||
},
|
||||
"use-fullscreen": {
|
||||
"label": "Tam ekran kullanılıyor"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Ambiyans Modu"
|
||||
},
|
||||
"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)"
|
||||
"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"
|
||||
},
|
||||
"blur-nav-bar": {
|
||||
"description": "Gezinme çubuğunu şeffaf ve bulanık yapar"
|
||||
"description": "Gezinme çubuğunu şeffaf ve bulanık yapar",
|
||||
"name": "Navigasyon barını bulanıklaştır"
|
||||
},
|
||||
"bypass-age-restrictions": {
|
||||
"description": "YouTube yaş doğrulamasını atla"
|
||||
"description": "YouTube yaş doğrulamasını atla",
|
||||
"name": "Yaş doğrulamasını atla"
|
||||
},
|
||||
"captions-selector": {
|
||||
"description": "YouTube Music için altyazı seçici",
|
||||
"menu": {
|
||||
"autoload": "Son kullanılan altyazıyı otomatik olarak seç",
|
||||
"disable-captions": "Varsayılan olarak altyazı yok"
|
||||
},
|
||||
"name": "Altyazı Seçici",
|
||||
"prompt": {
|
||||
"selector": {
|
||||
"none": "Hiçbiri"
|
||||
"label": "Geçerli altyazı dili: {{language}}",
|
||||
"none": "Hiçbiri",
|
||||
"title": "Altyazı dilini seç"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"title": "Altyazı seçiciyi aç"
|
||||
}
|
||||
},
|
||||
"compact-sidebar": {
|
||||
"description": "Her zaman kompakt kenar çubugu"
|
||||
"description": "Her zaman kompakt kenar çubugu",
|
||||
"name": "Kompakt Kenar Çubuğu"
|
||||
},
|
||||
"crossfade": {
|
||||
"description": "Şarkılar arasında Çapraz Geçiş",
|
||||
"menu": {
|
||||
"advanced": "İleri düzey için"
|
||||
"advanced": "Gelişmiş"
|
||||
},
|
||||
"name": "Çapraz Geçiş [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
"fade-in-duration": "Güçlenme süresi (ms)",
|
||||
"fade-out-duration": "Zayıflama süresi (ms)",
|
||||
"fade-scaling": {
|
||||
"label": "Zayıflama Ölçeği",
|
||||
"linear": "Doğrusal",
|
||||
"logarithmic": "Logaritmik"
|
||||
}
|
||||
}
|
||||
},
|
||||
"seconds-before-end": "Bitişten N saniye önce çapraz geçiş"
|
||||
},
|
||||
"title": "Çapraz Geçiş ayarları"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -199,14 +315,33 @@
|
||||
"description": "Şarkıların otomatik olarak duraklatılmasını sağlar",
|
||||
"menu": {
|
||||
"apply-once": "Yalnızca ilk şarkı için geçerlidir"
|
||||
}
|
||||
},
|
||||
"name": "Otomatik oynatmayı devre dışı bırak"
|
||||
},
|
||||
"discord": {
|
||||
"description": "Rich Presence ile Discord'da ne dinlediğinizi gösterin.",
|
||||
"backend": {
|
||||
"already-connected": "Aktif bağlantı olduğu halde bağlantı kurulmaya çalışıldı",
|
||||
"connected": "Discord'a bağlandı",
|
||||
"disconnected": "Discord ile bağlantı kesildi"
|
||||
},
|
||||
"description": "Rich Presence ile Discord'da ne dinlediğinizi gösterin",
|
||||
"menu": {
|
||||
"auto-reconnect": "Otomatik yeniden bağlan",
|
||||
"clear-activity": "Etkinliği temizle",
|
||||
"clear-activity-after-timeout": "Zaman aşımından sonra etkinliği temizle",
|
||||
"connected": "Bağlı",
|
||||
"disconnected": "Bağlantı kesildi",
|
||||
"hide-duration-left": "Kalan süreyi gizle",
|
||||
"hide-github-button": "GitHub bağlantısını gizle",
|
||||
"play-on-youtube-music": "YouTube Music de oynat",
|
||||
"set-inactivity-timeout": "Hareketsizlik zaman aşımını ayarla"
|
||||
},
|
||||
"name": "Discord Rich Presence",
|
||||
"prompt": {
|
||||
"set-inactivity-timeout": {
|
||||
"label": "Hareketsizlik zaman aşımını saniye cinsinden girin:",
|
||||
"title": "Hareketsizlik zaman aşımını ayarla"
|
||||
}
|
||||
}
|
||||
},
|
||||
"downloader": {
|
||||
@ -216,71 +351,298 @@
|
||||
"buttons": {
|
||||
"ok": "Tamam"
|
||||
},
|
||||
"title": "İndirmede hata meydana geldi!"
|
||||
"message": "Argh! Özür dilerim, indirme başarısız oldu…",
|
||||
"title": "İndirme sırasında bir hata meydana geldi!"
|
||||
},
|
||||
"start-download-playlist": {
|
||||
"buttons": {
|
||||
"ok": "Tamam"
|
||||
},
|
||||
"message": "Çalma listesini indir : {{playlistTitle}}",
|
||||
"detail": "({{playlistSize}} şarkı)",
|
||||
"message": "Oynatma listesini indir : {{playlistTitle}}",
|
||||
"title": "İndirme Başladı"
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"conversion-progress": "Dönüştürme : {{percent}}%",
|
||||
"converting": "Dönüştürülüyor…",
|
||||
"done": "Tamamlandı: {{filePath}}",
|
||||
"download-info": "{{artist}} - {{title}} [{{videoId}} indiriliyor",
|
||||
"download-progress": "İndirme : {{percent}}%",
|
||||
"preparing-file": "Dosya Hazırlanıyor…"
|
||||
"downloading": "İndiriliyor…",
|
||||
"downloading-counter": "İndiriliyor {{current}}/{{total}}…",
|
||||
"downloading-playlist": "\"{{playlistTitle}}\" şarkı listesi indiriliyor - {{playlistSize}} şarkı ({{playlistId}})",
|
||||
"error-while-downloading": "\"{{author}} - {{title}}\" indirilirken hata oluştu: {{error}}",
|
||||
"folder-already-exists": "{{playlistFolder}} klasörü zaten mevcut",
|
||||
"getting-playlist-info": "Oynatma listesi bilgisi alınıyor…",
|
||||
"loading": "Yükleniyor…",
|
||||
"playlist-has-only-one-song": "Oynatma listesinde yalnızca bir şarkı var, doğrudan indiriliyor",
|
||||
"playlist-id-not-found": "Oynatma listesi ID'si bulunamadı",
|
||||
"playlist-is-empty": "Oynatma listesi boş",
|
||||
"playlist-is-mix-or-private": "Çalma listesi bilgisi alınırken hata oluştu: özel veya \"Size özel karışık\" bir çalma listesi olmadığından emin olun\n\n{{error}}",
|
||||
"preparing-file": "Dosya Hazırlanıyor…",
|
||||
"saving": "Kaydediliyor…",
|
||||
"trying-to-get-playlist-id": "Çalma listesi ID'si alınmaya çalışılıyor: {{playlistId}}",
|
||||
"video-id-not-found": "Video bulunamadı",
|
||||
"writing-id3": "ID3 etiketleri yazılıyor…"
|
||||
}
|
||||
},
|
||||
"description": "MP3 / kaynak sesini doğrudan arayüzden indir",
|
||||
"menu": {
|
||||
"choose-download-folder": "İndirme klasörünü seç",
|
||||
"download-playlist": "Oynatma listesini indir",
|
||||
"presets": "Hazır Ayarlar",
|
||||
"skip-existing": "Mevcut dosyaları atla"
|
||||
},
|
||||
"name": "İndirici",
|
||||
"renderer": {
|
||||
"can-not-update-progress": "İlerleme güncellenemiyor"
|
||||
},
|
||||
"templates": {
|
||||
"button": "İndir"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"in-app-menu": {
|
||||
"description": "Menü çubuklarına süslü, koyu veya albüm renginde bir görünüm verir",
|
||||
"menu": {
|
||||
"hide-dom-window-controls": "DOM penceresi kontrollerini gizle"
|
||||
},
|
||||
"name": "Uygulama İçi Menü"
|
||||
},
|
||||
"last-fm": {
|
||||
"description": "Last.fm için scrobbling desteği ekler",
|
||||
"name": "Last.fm"
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Lumia Stream desteği ekler",
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Çoğu şarkı için şarkı sözü desteği ekler",
|
||||
"menu": {
|
||||
"romanized-lyrics": "Romanlaştırılmış Şarkı Sözleri"
|
||||
},
|
||||
"name": "Genius Şarkı Sözleri",
|
||||
"renderer": {
|
||||
"fetched-lyrics": "Şarkı sözleri genius tarafından alındı"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"description": "Oynatma listesini başkalarıyla paylaşın. Sunucu sahibi bir şarkı çaldığında, diğer herkes aynı şarkıyı duyacak",
|
||||
"dialog": {
|
||||
"enter-host": "Sunucu ID'si Girin"
|
||||
},
|
||||
"internal": {
|
||||
"save": "Kaydet",
|
||||
"track-source": "Parça Kaynağı",
|
||||
"unknown-user": "Bilinmeyen Kullanıcı"
|
||||
},
|
||||
"menu": {
|
||||
"click-to-copy-id": "Sunucu ID'sini kopyala",
|
||||
"close": "Birlikte Müziği Kapat",
|
||||
"connected-users": "Bağlanan Kullanıcılar",
|
||||
"disconnect": "Birlikte Müzik Bağlantısını Kesin",
|
||||
"empty-user": "Bağlı kullanıcı bulunmuyor",
|
||||
"host": "Birlikte Müzik Sunucusu",
|
||||
"join": "Birlikte Müziğe Katıl",
|
||||
"permission": {
|
||||
"all": "Konukların oynatma listesini ve oynatıcıyı kontrol etmesine izin verin",
|
||||
"host-only": "Çalma listesini ve oynatıcıyı yalnızca yönetici kontrol edebilir",
|
||||
"playlist": "Konukların oynatma listesini kontrol etmesine izin ver"
|
||||
},
|
||||
"set-permission": "Kontrol İznini Değiştir",
|
||||
"status": {
|
||||
"disconnected": "Bağlantı kesildi",
|
||||
"guest": "Misafir olarak bağlandı",
|
||||
"host": "Ev Sahibi olarak bağlandı"
|
||||
}
|
||||
},
|
||||
"name": "Birlikte Müzik [Beta]",
|
||||
"toast": {
|
||||
"add-song-failed": "Şarkı eklenirken bir hata meydana geldi",
|
||||
"closed": "Birlikte Müzik kapatıldı",
|
||||
"disconnected": "Birlikte Müzik bağlantı kesildi",
|
||||
"host-failed": "Birlikte Müzik sunucusu kurulamadı",
|
||||
"id-copied": "Sunucu ID'si kopyalandı",
|
||||
"join-failed": "Birlikte Müziğe katılırken bir hata meydana geldi",
|
||||
"joined": "Birlikte Müziğe Katıldı",
|
||||
"permission-changed": "Birlikte Müzik yetkisi \"{{permission}}\" olarak değiştirildi",
|
||||
"remove-song-failed": "Şarkı kaldırılırken bir hata meydana geldi",
|
||||
"user-connected": "{{name}} Birlikte Müziğe Katıldı",
|
||||
"user-disconnected": "{{name}} Birlikte Müzik'ten ayrıldı"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"description": "Favori tarayıcınızdaki gibi doğrudan arayüze entegre edilmiş İleri/Geri gezinme okları",
|
||||
"name": "Navigasyon"
|
||||
},
|
||||
"no-google-login": {
|
||||
"description": "Google giriş düğmelerini ve bağlantılarını arayüzden kaldır",
|
||||
"name": "Google Girişini Kaldır"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "Bir şarkı çalmaya başladığında bir bildirim görüntüler (etkileşimli bildirimler Windows'ta mevcuttur)",
|
||||
"menu": {
|
||||
"interactive": "İnteraktif Bildirimler",
|
||||
"interactive-settings": {
|
||||
"label": "İnteraktif Ayarlar",
|
||||
"submenu": {
|
||||
"hide-button-text": "Buton metnini gizle",
|
||||
"refresh-on-play-pause": "Oynat/Duraklat'ta Yenile",
|
||||
"tray-controls": "Tepsi tıklamasıyla Aç/Kapat"
|
||||
}
|
||||
},
|
||||
"priority": "Bildirim Önceliği",
|
||||
"toast-style": "Bildirim Tarzı",
|
||||
"unpause-notification": "Şarkı tekrar oynatılınca bildirim göster"
|
||||
},
|
||||
"name": "Bildirimler"
|
||||
},
|
||||
"shortcuts": {
|
||||
"prompt": {
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "İler"
|
||||
"picture-in-picture": {
|
||||
"description": "Uygulamayı resim-içinde-resim moduna geçirmeye izin verir",
|
||||
"menu": {
|
||||
"always-on-top": "Her zaman üstte",
|
||||
"hotkey": {
|
||||
"label": "Kısayol",
|
||||
"prompt": {
|
||||
"keybind-options": {
|
||||
"hotkey": "Kısayol"
|
||||
},
|
||||
"label": "Resim-içinde-resim arasında geçiş yapmak için bir kısayol tuşu seçin",
|
||||
"title": "Resim-içinde-resim Kısayol Tuşu"
|
||||
}
|
||||
},
|
||||
"save-window-position": "Pencere konumunu kaydet",
|
||||
"save-window-size": "Pencere boyutunu kaydet",
|
||||
"use-native-pip": "Tarayıcı yerel PiP'sini kullan"
|
||||
},
|
||||
"name": "Resim-içinde-resim",
|
||||
"templates": {
|
||||
"button": "Resim-içinde-resim"
|
||||
}
|
||||
},
|
||||
"playback-speed": {
|
||||
"description": "Hızlı dinle, yavaş dinle! Şarkı hızını kontrol eden bir kaydırıcı ekler",
|
||||
"name": "Oynatma Hızı",
|
||||
"templates": {
|
||||
"button": "Hız"
|
||||
}
|
||||
},
|
||||
"precise-volume": {
|
||||
"description": "Özel bir HUD ve özelleştirilebilir ses seviyesi adımları ile fare tekerleği / kısayol tuşlarını kullanarak ses seviyesini hassas bir şekilde kontrol edin",
|
||||
"menu": {
|
||||
"arrows-shortcuts": "Yerel Ok Tuşu Kontrolleri",
|
||||
"custom-volume-steps": "Özel Ses Seviyesi Adımlarını Ayarlama",
|
||||
"global-shortcuts": "Genel Kısayol Tuşları"
|
||||
},
|
||||
"name": "Hassas Ses Seviyesi",
|
||||
"prompt": {
|
||||
"global-shortcuts": {
|
||||
"keybind-options": {
|
||||
"decrease": "Ses Seviyesi Azaltma",
|
||||
"increase": "Ses Seviyesi Yükseltme"
|
||||
},
|
||||
"label": "Genel Ses Tuş Atamalarını seçin:",
|
||||
"title": "Genel Ses Tuş Atamaları"
|
||||
},
|
||||
"volume-steps": {
|
||||
"label": "Ses Artırma/Azaltma Kademelerini Seçin",
|
||||
"title": "Ses Kademeleri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quality-changer": {
|
||||
"backend": {
|
||||
"dialog": {
|
||||
"quality-changer": {
|
||||
"detail": "Mevcut Kalite: {{quality}}",
|
||||
"message": "Video Kalitesini Seçin:",
|
||||
"title": "Video Kalitesini Seçin"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Video katmanı üzerindeki bir düğme ile video kalitesinin değiştirilmesine izin verir",
|
||||
"name": "Video Kalitesi Değiştirici"
|
||||
},
|
||||
"shortcuts": {
|
||||
"description": "Oynatma için global kısayol tuşları (oynat/duraklat/sonraki/önceki) ayarlamaya ve medya tuşlarını geçersiz kılarak medya OSD'sini kapatmaya, arama yapmak için Ctrl/CMD + F tuşlarını açmaya, medya tuşları için Linux MPRIS desteğini açmaya ve ileri düzey kullanıcılar için özel kısayol tuşlarına izin verir.",
|
||||
"menu": {
|
||||
"override-media-keys": "Medya Tuşlarını Geçersiz Kıl",
|
||||
"set-keybinds": "Global Şarkı Kontrollerini Ayarla"
|
||||
},
|
||||
"name": "Kısayollar (& MPRIS)",
|
||||
"prompt": {
|
||||
"keybind": {
|
||||
"keybind-options": {
|
||||
"next": "İler",
|
||||
"play-pause": "Oynat / Durdur",
|
||||
"previous": "Önceki"
|
||||
},
|
||||
"label": "Şarkı Kontrolü için Genel Tuş Atamaları'nı seçin:",
|
||||
"title": "Genel Tuş Atamaları"
|
||||
}
|
||||
}
|
||||
},
|
||||
"skip-disliked-songs": {
|
||||
"description": "Beğenmediğin şarkıları atlar",
|
||||
"name": "Beğenmediklerini Atla"
|
||||
},
|
||||
"skip-silences": {
|
||||
"description": "Şarkılardaki sessiz bölümleri otomatik olarak atlar",
|
||||
"name": "Sessizlikleri Atla"
|
||||
},
|
||||
"sponsorblock": {
|
||||
"description": "Giriş/Çıkış gibi müzik olmayan kısımları veya müzik videolarında şarkının çalmadığı kısımları otomatik olarak atlar",
|
||||
"name": "SponsorBlock"
|
||||
},
|
||||
"taskbar-mediacontrol": {
|
||||
"description": "Windows görev çubuğu üzerinden oynatmayı kontrol edebilmenize olanak sağlar",
|
||||
"name": "Görev Çubuğu Medya Kontrolü"
|
||||
},
|
||||
"touchbar": {
|
||||
"description": "macOS kullanıcıları için bir TouchBar widget'ı ekler",
|
||||
"name": "TouchBar"
|
||||
},
|
||||
"tuna-obs": {
|
||||
"description": "OBS eklentisi Tuna ile entegrasyon sağlar",
|
||||
"name": "Tuna OBS"
|
||||
},
|
||||
"video-toggle": {
|
||||
"description": "Video/Şarkı modu arasında geçiş yapmak için bir düğme ekler. ayrıca isteğe bağlı olarak tüm video sekmesini kaldırabilir",
|
||||
"menu": {
|
||||
"align": {
|
||||
"label": "Hizalama",
|
||||
"submenu": {
|
||||
"left": "Sol",
|
||||
"middle": "Orta",
|
||||
"right": "Sağ"
|
||||
}
|
||||
},
|
||||
"force-hide": "Video sekmesini kaldırmaya zorla",
|
||||
"mode": {
|
||||
"label": "Mod"
|
||||
"label": "Mod",
|
||||
"submenu": {
|
||||
"custom": "Özel Ayar",
|
||||
"disabled": "Devre dışı",
|
||||
"native": "Yerel geçiş"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Video Geçiş",
|
||||
"templates": {
|
||||
"button": "Şarkı"
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "Oynatıcıya bir görselleştirici ekler",
|
||||
"menu": {
|
||||
"visualizer-type": "Görselleştirici Tipi"
|
||||
},
|
||||
"name": "Görselleştirici"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
21
src/i18n/resources/vi.json
Normal file
21
src/i18n/resources/vi.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"common": {
|
||||
"console": {
|
||||
"plugins": {
|
||||
"execute-failed": "Lỗi khi bắt đầu phần mở rộng {{pluginName}}::{{contextName}}",
|
||||
"executed-at-ms": "Phần mở rộng {{pluginName}}::{{contextName}} đã bắt đầu trong {{ms}}ms",
|
||||
"initialize-failed": "Lỗi khi khởi động phần mở rộng \"{{pluginName}}\"",
|
||||
"load-all": "Đang tải tất cả phần mở rộng",
|
||||
"load-failed": "Lỗi khi tải phần mở rộng\"{{pluginName}}\"",
|
||||
"loaded": "Đã tải phần mở rộng \"{{pluginName}}\"",
|
||||
"unload-failed": "Lỗi khi hủy tải phần mở rộng \"{{pluginName}}\"",
|
||||
"unloaded": "Đã hủy tải phần mở rộng \"{{pluginName}}\""
|
||||
}
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"code": "vi",
|
||||
"local-name": "Tiếng Việt",
|
||||
"name": "Vietnamese"
|
||||
}
|
||||
}
|
||||
@ -170,7 +170,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"enabled": "已启用",
|
||||
"label": "插件"
|
||||
"label": "插件",
|
||||
"new": "新建"
|
||||
},
|
||||
"view": {
|
||||
"label": "视图",
|
||||
@ -201,6 +202,10 @@
|
||||
},
|
||||
"name": "广告屏蔽器"
|
||||
},
|
||||
"album-actions": {
|
||||
"description": "添加作用于播放列表或专辑中所有歌曲的全局“点赞/取消点赞”与“喜欢/取消喜欢”按钮。",
|
||||
"name": "专辑操作"
|
||||
},
|
||||
"album-color-theme": {
|
||||
"description": "根据专辑封面配色动态改变主题与视觉效果",
|
||||
"name": "专辑配色主题"
|
||||
@ -289,7 +294,7 @@
|
||||
"menu": {
|
||||
"advanced": "高级"
|
||||
},
|
||||
"name": "交叉淡化 [beta]",
|
||||
"name": "交叉淡化 [Beta]",
|
||||
"prompt": {
|
||||
"options": {
|
||||
"multi-input": {
|
||||
@ -426,6 +431,51 @@
|
||||
"fetched-lyrics": "已从 Genius 获取字幕"
|
||||
}
|
||||
},
|
||||
"music-together": {
|
||||
"description": "与他人共享播放列表。当发起人播放歌曲时,其他人也会听到相同歌曲",
|
||||
"dialog": {
|
||||
"enter-host": "输入发起人 ID"
|
||||
},
|
||||
"internal": {
|
||||
"save": "保存",
|
||||
"track-source": "追踪来源",
|
||||
"unknown-user": "未知用户"
|
||||
},
|
||||
"menu": {
|
||||
"click-to-copy-id": "复制发起者 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 复制到剪切板",
|
||||
"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": "导航"
|
||||
|
||||
@ -687,13 +687,15 @@ app.whenReady().then(async () => {
|
||||
const dialogOptions: Electron.MessageBoxOptions = {
|
||||
type: 'info',
|
||||
buttons: [
|
||||
t('main.dialog.update-available.buttons.download'),
|
||||
t('main.dialog.update-available.buttons.ok'),
|
||||
t('main.dialog.update-available.buttons.download'),
|
||||
t('main.dialog.update-available.buttons.disable'),
|
||||
],
|
||||
title: t('main.dialog.update-available.title'),
|
||||
message: t('main.dialog.update-available.message'),
|
||||
detail: t('main.dialog.update-available.detail', { downloadLink }),
|
||||
defaultId: 1,
|
||||
cancelId: 0,
|
||||
};
|
||||
|
||||
let dialogPromise: Promise<Electron.MessageBoxReturnValue>;
|
||||
@ -717,7 +719,7 @@ app.whenReady().then(async () => {
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
case 0: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ export default createPlugin({
|
||||
name: () => t('plugins.album-actions.name'),
|
||||
description: () => t('plugins.album-actions.description'),
|
||||
restartNeeded: false,
|
||||
addedVersion: '3.2.0',
|
||||
addedVersion: '3.2.X',
|
||||
config: {
|
||||
enabled: false,
|
||||
},
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import prompt from 'custom-electron-prompt';
|
||||
|
||||
import { DataConnection } from 'peerjs';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
import { createPlugin } from '@/utils';
|
||||
import promptOptions from '@/providers/prompt-options';
|
||||
@ -17,7 +19,6 @@ 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 { DataConnection } from 'peerjs';
|
||||
|
||||
type RawAccountData = {
|
||||
accountName: {
|
||||
@ -34,59 +35,84 @@ type RawAccountData = {
|
||||
};
|
||||
};
|
||||
|
||||
export default createPlugin({
|
||||
export default createPlugin<
|
||||
unknown,
|
||||
unknown,
|
||||
{
|
||||
connection?: Connection;
|
||||
ipc?: RendererContext<never>['ipc'];
|
||||
api: HTMLElement & AppAPI | 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;
|
||||
}
|
||||
>({
|
||||
name: () => t('plugins.music-together.name'),
|
||||
description: () => t('plugins.music-together.description'),
|
||||
restartNeeded: false,
|
||||
addedVersion: '3.2.0',
|
||||
addedVersion: '3.2.X',
|
||||
config: {
|
||||
enabled: false
|
||||
},
|
||||
stylesheets: [style],
|
||||
backend: {
|
||||
async start({ ipc }) {
|
||||
ipc.handle('music-together:prompt', async (title: string, label: string) => prompt({
|
||||
title,
|
||||
label,
|
||||
type: 'input',
|
||||
...promptOptions()
|
||||
}));
|
||||
}
|
||||
backend({ ipc }) {
|
||||
ipc.handle('music-together:prompt', async (title: string, label: string) => prompt({
|
||||
title,
|
||||
label,
|
||||
type: 'input',
|
||||
...promptOptions()
|
||||
}));
|
||||
},
|
||||
renderer: {
|
||||
connection: null as Connection | null,
|
||||
ipc: null as RendererContext<never>['ipc'] | null,
|
||||
|
||||
api: null as (HTMLElement & AppAPI) | null,
|
||||
queue: null as Queue | null,
|
||||
playerApi: null as YoutubePlayer | null,
|
||||
showPrompt: (async () => null) as ((title: string, label: string) => Promise<string | null>),
|
||||
|
||||
elements: {} as {
|
||||
setting: HTMLElement;
|
||||
icon: SVGElement;
|
||||
spinner: HTMLElement;
|
||||
},
|
||||
updateNext: false,
|
||||
ignoreChange: false,
|
||||
permission: 'playlist',
|
||||
popups: {} as {
|
||||
host: ReturnType<typeof createHostPopup>;
|
||||
guest: ReturnType<typeof createGuestPopup>;
|
||||
setting: ReturnType<typeof createSettingPopup>;
|
||||
},
|
||||
stateInterval: null as number | null,
|
||||
updateNext: false,
|
||||
ignoreChange: false,
|
||||
rollbackInjector: null as (() => void) | null,
|
||||
|
||||
me: null as Omit<Profile, 'id'> | null,
|
||||
profiles: {} as Record<string, Profile>,
|
||||
permission: 'playlist' as Permission,
|
||||
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: any) => ({
|
||||
videoId: it.videoId,
|
||||
const videoList: VideoData[] = this.queue?.flatItems.map((it) => ({
|
||||
videoId: it!.videoId,
|
||||
ownerId: this.connection!.id
|
||||
} satisfies VideoData)) ?? [];
|
||||
|
||||
@ -123,8 +149,8 @@ export default createPlugin({
|
||||
if (!wait) return false;
|
||||
|
||||
if (!this.me) this.me = getDefaultProfile(this.connection.id);
|
||||
const rawItems = this.queue?.flatItems?.map((it: any) => ({
|
||||
videoId: it.videoId,
|
||||
const rawItems = this.queue?.flatItems?.map((it) => ({
|
||||
videoId: it!.videoId,
|
||||
ownerId: this.connection!.id
|
||||
} satisfies VideoData)) ?? [];
|
||||
this.queue?.setOwner({
|
||||
@ -170,7 +196,7 @@ export default createPlugin({
|
||||
case 'REMOVE_SONG': {
|
||||
if (conn && this.permission === 'host-only') return;
|
||||
|
||||
await this.queue?.removeVideo(event.payload.index);
|
||||
this.queue?.removeVideo(event.payload.index);
|
||||
await this.connection?.broadcast('REMOVE_SONG', event.payload);
|
||||
break;
|
||||
}
|
||||
@ -295,11 +321,11 @@ export default createPlugin({
|
||||
break;
|
||||
}
|
||||
case 'REMOVE_SONG': {
|
||||
await this.queue?.removeVideo(event.payload.index);
|
||||
this.queue?.removeVideo(event.payload.index);
|
||||
break;
|
||||
}
|
||||
case 'MOVE_SONG': {
|
||||
await this.queue?.moveItem(event.payload.fromIndex, event.payload.toIndex);
|
||||
this.queue?.moveItem(event.payload.fromIndex, event.payload.toIndex);
|
||||
break;
|
||||
}
|
||||
case 'IDENTIFY': {
|
||||
@ -461,7 +487,7 @@ export default createPlugin({
|
||||
this.queue?.removeQueueOwner();
|
||||
if (this.rollbackInjector) {
|
||||
this.rollbackInjector();
|
||||
this.rollbackInjector = null;
|
||||
this.rollbackInjector = undefined;
|
||||
}
|
||||
|
||||
this.profiles = {};
|
||||
@ -530,7 +556,7 @@ export default createPlugin({
|
||||
|
||||
start({ ipc }) {
|
||||
this.ipc = ipc;
|
||||
this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label);
|
||||
this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label) as Promise<string>;
|
||||
this.api = document.querySelector<HTMLElement & AppAPI>('ytmusic-app');
|
||||
|
||||
/* setup */
|
||||
@ -571,10 +597,15 @@ export default createPlugin({
|
||||
}
|
||||
|
||||
if (id === 'music-together-copy-id') {
|
||||
navigator.clipboard.writeText(this.connection?.id ?? '');
|
||||
|
||||
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
||||
hostPopup.dismiss();
|
||||
navigator.clipboard.writeText(this.connection?.id ?? '')
|
||||
.then(() => {
|
||||
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
||||
hostPopup.dismiss();
|
||||
})
|
||||
.catch(() => {
|
||||
this.api?.openToast(t('plugins.music-together.toast.id-copy-failed'));
|
||||
hostPopup.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
if (id === 'music-together-permission') {
|
||||
@ -614,9 +645,14 @@ export default createPlugin({
|
||||
this.hideSpinner();
|
||||
|
||||
if (result) {
|
||||
navigator.clipboard.writeText(this.connection?.id ?? '');
|
||||
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
||||
hostPopup.showAtAnchor(setting);
|
||||
navigator.clipboard.writeText(this.connection?.id ?? '')
|
||||
.then(() => {
|
||||
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
||||
hostPopup.showAtAnchor(setting);
|
||||
}).catch(() => {
|
||||
this.api?.openToast(t('plugins.music-together.toast.id-copy-failed'));
|
||||
hostPopup.showAtAnchor(setting);
|
||||
});
|
||||
} else {
|
||||
this.api?.openToast(t('plugins.music-together.toast.host-failed'));
|
||||
}
|
||||
@ -642,7 +678,7 @@ export default createPlugin({
|
||||
guest: guestPopup,
|
||||
setting: settingPopup
|
||||
};
|
||||
setting.addEventListener('click', async () => {
|
||||
setting.addEventListener('click', () => {
|
||||
let popup = settingPopup;
|
||||
if (this.connection?.mode === 'host') popup = hostPopup;
|
||||
if (this.connection?.mode === 'guest') popup = guestPopup;
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import { SHA1Hash } from './sha1hash';
|
||||
|
||||
export const extractToken = (cookie = document.cookie) => cookie.match(/SAPISID=([^;]+);/)?.[1] ?? cookie.match(/__Secure\-3PAPISID=([^;]+);/)?.[1];
|
||||
export const extractToken = (cookie = document.cookie) => cookie.match(/SAPISID=([^;]+);/)?.[1] ?? cookie.match(/__Secure-3PAPISID=([^;]+);/)?.[1];
|
||||
|
||||
export const getHash = (papisid: string, millis = Date.now(), origin: string = 'https://music.youtube.com') => {
|
||||
const hash = SHA1Hash();
|
||||
hash.update(`${millis} ${papisid} ${origin}`);
|
||||
return hash.digestString().toLowerCase();
|
||||
};
|
||||
export const getHash = async (papisid: string, millis = Date.now(), origin: string = 'https://music.youtube.com') =>
|
||||
(await SHA1Hash(`${millis} ${papisid} ${origin}`)).toLowerCase();
|
||||
|
||||
export const getAuthorizationHeader = (papisid: string, millis = Date.now(), origin: string = 'https://music.youtube.com') => {
|
||||
return `SAPISIDHASH ${millis}_${getHash(papisid, millis, origin)}`;
|
||||
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 = () => {
|
||||
|
||||
@ -1,12 +1,51 @@
|
||||
import { getMusicQueueRenderer } from './song';
|
||||
import { mapQueueItem } from './utils';
|
||||
|
||||
import type { Profile, QueueAPI, VideoData } from '../types';
|
||||
import { ConnectionEventUnion } from '@/plugins/music-together/connection';
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import type { Profile, QueueAPI, VideoData } from '../types';
|
||||
import type { QueueItem } from '@/types/datahost-get-state';
|
||||
|
||||
const getHeaderPayload = (() => {
|
||||
let payload: unknown = null;
|
||||
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) {
|
||||
@ -58,7 +97,7 @@ const getHeaderPayload = (() => {
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
export type QueueOptions = {
|
||||
@ -70,11 +109,11 @@ export type QueueOptions = {
|
||||
export type QueueEventListener = (event: ConnectionEventUnion) => void;
|
||||
|
||||
export class Queue {
|
||||
private queue: (HTMLElement & QueueAPI) | null = null;
|
||||
private originalDispatch: ((obj: {
|
||||
private queue: (HTMLElement & QueueAPI);
|
||||
private originalDispatch?: (obj: {
|
||||
type: string;
|
||||
payload?: unknown;
|
||||
}) => void) | null = null;
|
||||
payload?: { items?: QueueItem[] | undefined; };
|
||||
}) => void;
|
||||
private internalDispatch = false;
|
||||
private ignoreFlag = false;
|
||||
private listeners: QueueEventListener[] = [];
|
||||
@ -83,7 +122,7 @@ export class Queue {
|
||||
|
||||
constructor(options: QueueOptions) {
|
||||
this.getProfile = options.getProfile;
|
||||
this.queue = options.queue ?? document.querySelector<HTMLElement & QueueAPI>('#queue');
|
||||
this.queue = options.queue ?? document.querySelector<HTMLElement & QueueAPI>('#queue')!;
|
||||
this.owner = options.owner ?? null;
|
||||
this._videoList = options.videoList ?? [];
|
||||
}
|
||||
@ -96,7 +135,7 @@ export class Queue {
|
||||
}
|
||||
|
||||
get selectedIndex() {
|
||||
return mapQueueItem((it) => it?.selected, this.queue?.store.getState().queue.items).findIndex(Boolean) ?? 0;
|
||||
return mapQueueItem((it) => it?.selected, this.queue.store.getState().queue.items).findIndex(Boolean) ?? 0;
|
||||
}
|
||||
|
||||
get rawItems() {
|
||||
@ -146,7 +185,7 @@ export class Queue {
|
||||
return true;
|
||||
}
|
||||
|
||||
async removeVideo(index: number) {
|
||||
removeVideo(index: number) {
|
||||
this.internalDispatch = true;
|
||||
this._videoList.splice(index, 1);
|
||||
this.queue?.dispatch({
|
||||
@ -233,10 +272,10 @@ export class Queue {
|
||||
if (event.type === 'ADD_ITEMS') {
|
||||
if (this.ignoreFlag) {
|
||||
this.ignoreFlag = false;
|
||||
const videoList = mapQueueItem((it: any) => ({
|
||||
videoId: it.videoId,
|
||||
const videoList = mapQueueItem((it) => ({
|
||||
videoId: it!.videoId,
|
||||
ownerId: this.owner!.id
|
||||
} satisfies VideoData), (event.payload as any).items);
|
||||
} satisfies VideoData), event.payload!.items!);
|
||||
const index = this._videoList.length + videoList.length - 1;
|
||||
|
||||
if (videoList.length > 0) {
|
||||
@ -255,15 +294,17 @@ export class Queue {
|
||||
]
|
||||
});
|
||||
}
|
||||
} else if ((event.payload as any).items.length === 1) {
|
||||
} 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: any) => ({
|
||||
videoId: it.videoId,
|
||||
videoList: mapQueueItem((it) => ({
|
||||
videoId: it!.videoId,
|
||||
ownerId: this.owner!.id
|
||||
} satisfies VideoData), (event.payload as any).items)
|
||||
} satisfies VideoData), event.payload!.items!)
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -275,8 +316,12 @@ export class Queue {
|
||||
this.broadcast({
|
||||
type: 'MOVE_SONG',
|
||||
payload: {
|
||||
fromIndex: (event.payload as any).fromIndex,
|
||||
toIndex: (event.payload as any).toIndex
|
||||
fromIndex: (event.payload as {
|
||||
fromIndex: number;
|
||||
}).fromIndex,
|
||||
toIndex: (event.payload as {
|
||||
toIndex: number;
|
||||
}).toIndex
|
||||
}
|
||||
});
|
||||
return;
|
||||
@ -306,7 +351,7 @@ export class Queue {
|
||||
event.payload = undefined;
|
||||
}
|
||||
if (event.type === 'SET_PLAYER_UI_STATE') {
|
||||
if (event.payload === 'INACTIVE' && this.videoList.length > 0) {
|
||||
if (event.payload as string === 'INACTIVE' && this.videoList.length > 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -321,12 +366,12 @@ export class Queue {
|
||||
dispatch: this.originalDispatch
|
||||
}
|
||||
};
|
||||
this.originalDispatch!.call(fakeContext, event);
|
||||
this.originalDispatch?.call(fakeContext, event);
|
||||
};
|
||||
}
|
||||
|
||||
/* sync */
|
||||
async initQueue() {
|
||||
initQueue() {
|
||||
if (!this.queue) return;
|
||||
|
||||
this.internalDispatch = true;
|
||||
@ -369,13 +414,13 @@ export class Queue {
|
||||
return true;
|
||||
}
|
||||
|
||||
async syncQueueOwner() {
|
||||
syncQueueOwner() {
|
||||
const allQueue = document.querySelectorAll('#queue');
|
||||
|
||||
allQueue.forEach((queue) => {
|
||||
const list = Array.from(queue?.querySelectorAll<HTMLElement>('ytmusic-player-queue-item') ?? []);
|
||||
|
||||
list.forEach((item, index) => {
|
||||
list.forEach((item, index: number | undefined) => {
|
||||
if (typeof index !== 'number') return;
|
||||
|
||||
const id = this._videoList[index]?.ownerId;
|
||||
|
||||
@ -1,117 +1,7 @@
|
||||
export function SHA1Hash(): {
|
||||
reset: () => void,
|
||||
update: (message: string | number[], length?: number) => void,
|
||||
digest: () => number[],
|
||||
digestString: () => string
|
||||
} {
|
||||
let hash: number[];
|
||||
|
||||
function initialize(): void {
|
||||
hash = [1732584193, 4023233417, 2562383102, 271733878, 3285377520];
|
||||
totalLength = currentLength = 0;
|
||||
}
|
||||
|
||||
function processBlock(block: number[]): void {
|
||||
let words: number[] = [];
|
||||
for (let i = 0; i < 64; i += 4) {
|
||||
words[i / 4] = block[i] << 24 | block[i + 1] << 16 | block[i + 2] << 8 | block[i + 3];
|
||||
}
|
||||
|
||||
for (let i = 16; i < 80; i++) {
|
||||
let temp = words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16];
|
||||
words[i] = (temp << 1 | temp >>> 31) & 4294967295;
|
||||
}
|
||||
|
||||
let a = hash[0],
|
||||
b = hash[1],
|
||||
c = hash[2],
|
||||
d = hash[3],
|
||||
e = hash[4];
|
||||
for (let i = 0; i < 80; i++) {
|
||||
let f, k;
|
||||
if (i < 20) {
|
||||
f = d ^ b & (c ^ d);
|
||||
k = 1518500249;
|
||||
} else if (i < 40) {
|
||||
f = b ^ c ^ d;
|
||||
k = 1859775393;
|
||||
} else if (i < 60) {
|
||||
f = b & c | d & (b | c);
|
||||
k = 2400959708;
|
||||
} else {
|
||||
f = b ^ c ^ d;
|
||||
k = 3395469782;
|
||||
}
|
||||
let temp = ((a << 5 | a >>> 27) & 4294967295) + f + e + k + words[i] & 4294967295;
|
||||
e = d;
|
||||
d = c;
|
||||
c = (b << 30 | b >>> 2) & 4294967295;
|
||||
b = a;
|
||||
a = temp;
|
||||
}
|
||||
hash[0] = hash[0] + a & 4294967295;
|
||||
hash[1] = hash[1] + b & 4294967295;
|
||||
hash[2] = hash[2] + c & 4294967295;
|
||||
hash[3] = hash[3] + d & 4294967295;
|
||||
hash[4] = hash[4] + e & 4294967295;
|
||||
}
|
||||
|
||||
function update(message: string | number[], length?: number): void {
|
||||
if ('string' === typeof message) {
|
||||
message = unescape(encodeURIComponent(message));
|
||||
let bytes: number[] = [];
|
||||
for (let i = 0, len = message.length; i < len; ++i)
|
||||
bytes.push(message.charCodeAt(i));
|
||||
message = bytes;
|
||||
}
|
||||
length || (length = message.length);
|
||||
let i = 0;
|
||||
if (0 == currentLength)
|
||||
for (; i + 64 < length;)
|
||||
processBlock(message.slice(i, i + 64)),
|
||||
i += 64,
|
||||
totalLength += 64;
|
||||
for (; i < length;)
|
||||
if (buffer[currentLength++] = message[i++],
|
||||
totalLength++,
|
||||
64 == currentLength)
|
||||
for (currentLength = 0,
|
||||
processBlock(buffer); i + 64 < length;)
|
||||
processBlock(message.slice(i, i + 64)),
|
||||
i += 64,
|
||||
totalLength += 64;
|
||||
}
|
||||
|
||||
function finalize(): number[] {
|
||||
let result: number[] = []
|
||||
, bits = 8 * totalLength;
|
||||
if (currentLength < 56)
|
||||
update(padding, 56 - currentLength);
|
||||
else
|
||||
update(padding, 64 - (currentLength - 56));
|
||||
for (let i = 63; i >= 56; i--)
|
||||
buffer[i] = bits & 255,
|
||||
bits >>>= 8;
|
||||
processBlock(buffer);
|
||||
for (let i = 0; i < 5; i++)
|
||||
for (let j = 24; j >= 0; j -= 8)
|
||||
result.push(hash[i] >> j & 255);
|
||||
return result;
|
||||
}
|
||||
|
||||
let buffer: number[] = [], padding: number[] = [128], totalLength: number, currentLength: number;
|
||||
for (let i = 1; i < 64; ++i)
|
||||
padding[i] = 0;
|
||||
initialize();
|
||||
return {
|
||||
reset: initialize,
|
||||
update: update,
|
||||
digest: finalize,
|
||||
digestString: function(): string {
|
||||
let hash = finalize(), hex = '';
|
||||
for (let i = 0; i < hash.length; i++)
|
||||
hex += '0123456789ABCDEF'.charAt(Math.floor(hash[i] / 16)) + '0123456789ABCDEF'.charAt(hash[i] % 16);
|
||||
return hex;
|
||||
}
|
||||
};
|
||||
}
|
||||
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('');
|
||||
};
|
||||
|
||||
@ -33,8 +33,8 @@ export const getMusicQueueRenderer = async (videoIds: string[]): Promise<QueueRe
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Origin: 'https://music.youtube.com',
|
||||
Authorization: getAuthorizationHeader(token)
|
||||
'Origin': 'https://music.youtube.com',
|
||||
'Authorization': await getAuthorizationHeader(token),
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
export const mapQueueItem = <T>(map: (item: any | null) => T, array: any[]): T[] => array
|
||||
import {
|
||||
ItemPlaylistPanelVideoRenderer,
|
||||
PlaylistPanelVideoWrapperRenderer,
|
||||
QueueItem
|
||||
} from '@/types/datahost-get-state';
|
||||
|
||||
export const mapQueueItem = <T>(map: (item?: ItemPlaylistPanelVideoRenderer) => T, array: QueueItem[]): T[] => array
|
||||
.map((item) => {
|
||||
if ('playlistPanelVideoWrapperRenderer' in item) {
|
||||
const keys = Object.keys(item.playlistPanelVideoWrapperRenderer.primaryRenderer);
|
||||
return item.playlistPanelVideoWrapperRenderer.primaryRenderer[keys[0]];
|
||||
const keys = Object.keys(item.playlistPanelVideoWrapperRenderer!.primaryRenderer) as (keyof PlaylistPanelVideoWrapperRenderer['primaryRenderer'])[];
|
||||
return item.playlistPanelVideoWrapperRenderer!.primaryRenderer[keys[0]];
|
||||
}
|
||||
if ('playlistPanelVideoRenderer' in item) {
|
||||
return item.playlistPanelVideoRenderer;
|
||||
}
|
||||
|
||||
console.error('Music Together: Unknown item', item);
|
||||
return null;
|
||||
return undefined;
|
||||
})
|
||||
.map(map);
|
||||
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
<div class="music-together-status">
|
||||
<div class="music-together-status-container">
|
||||
<img class="music-together-profile big">
|
||||
<img class="music-together-profile big" alt="Profile Image">
|
||||
<div class="music-together-status-item">
|
||||
<ytmd-trans key="plugins.music-together.name"></ytmd-trans>
|
||||
<span id="music-together-status-label">
|
||||
<ytmd-trans key="plugins.music-together.menu.status.disconnected"></ytmd-trans>
|
||||
</span>
|
||||
<span id="music-together-permission-label">
|
||||
<marquee id="music-together-permission-label">
|
||||
<ytmd-trans key="plugins.music-together.menu.permission.playlist" style="color: rgba(255, 255, 255, 0.75)"></ytmd-trans>
|
||||
</span>
|
||||
</marquee>
|
||||
</div>
|
||||
</div>
|
||||
<div class="music-together-divider horizontal" style="margin: 16px 0;"></div>
|
||||
|
||||
@ -1,15 +1,19 @@
|
||||
import { YoutubePlayer } from '@/types/youtube-player';
|
||||
type StoreState = any;
|
||||
import { GetState, QueueItem } from '@/types/datahost-get-state';
|
||||
|
||||
type StoreState = GetState;
|
||||
type Store = {
|
||||
dispatch: (obj: {
|
||||
type: string;
|
||||
payload?: unknown;
|
||||
payload?: {
|
||||
items?: QueueItem[];
|
||||
};
|
||||
}) => void;
|
||||
|
||||
getState: () => StoreState;
|
||||
replaceReducer: (param1: unknown) => unknown;
|
||||
subscribe: (callback: () => void) => unknown;
|
||||
};
|
||||
}
|
||||
export type QueueAPI = {
|
||||
dispatch(obj: {
|
||||
type: string;
|
||||
@ -28,8 +32,6 @@ export type AppAPI = {
|
||||
// TODO: Add more
|
||||
};
|
||||
|
||||
|
||||
|
||||
export type Profile = {
|
||||
id: string;
|
||||
handleId: string;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { ElementFromHtml } from '@/plugins/utils/renderer';
|
||||
import statusHTML from '../templates/status.html?raw';
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import statusHTML from '../templates/status.html?raw';
|
||||
|
||||
import type { Permission, Profile } from '../types';
|
||||
|
||||
export const createStatus = () => {
|
||||
@ -9,7 +11,7 @@ export const createStatus = () => {
|
||||
|
||||
const profile = element.querySelector<HTMLImageElement>('.music-together-profile')!;
|
||||
const statusLabel = element.querySelector<HTMLSpanElement>('#music-together-status-label')!;
|
||||
const permissionLabel = element.querySelector<HTMLSpanElement>('#music-together-permission-label')!;
|
||||
const permissionLabel = element.querySelector<HTMLMarqueeElement>('#music-together-permission-label')!;
|
||||
|
||||
profile.src = icon?.src ?? '';
|
||||
|
||||
|
||||
@ -40,7 +40,6 @@ const audioCanPlayListener = (e: CustomEvent<Compressor>) => {
|
||||
const fftBins = new Float32Array(analyser.frequencyBinCount);
|
||||
|
||||
sourceNode.connect(analyser);
|
||||
analyser.connect(audioContext.destination);
|
||||
|
||||
const looper = () => {
|
||||
setTimeout(() => {
|
||||
|
||||
@ -127,7 +127,7 @@ export default (api: YoutubePlayer) => {
|
||||
|
||||
const waitingEvent = new Set<string>();
|
||||
// Name = "dataloaded" and abit later "dataupdated"
|
||||
api.addEventListener('videodatachange', (name: string, videoData) => {
|
||||
api.addEventListener('videodatachange', (name, videoData) => {
|
||||
videoEventDispatcher(name, videoData);
|
||||
|
||||
if (name === 'dataupdated' && waitingEvent.has(videoData.videoId)) {
|
||||
|
||||
28
src/tray.ts
28
src/tray.ts
@ -1,10 +1,12 @@
|
||||
import { Menu, nativeImage, Tray } from 'electron';
|
||||
|
||||
import youtubeMusicTrayIcon from '@assets/youtube-music-tray.png?asset&asarUnpack';
|
||||
import defaultTrayIconAsset from '@assets/youtube-music-tray.png?asset&asarUnpack';
|
||||
import pausedTrayIconAsset from '@assets/youtube-music-tray-paused.png?asset&asarUnpack';
|
||||
|
||||
import config from './config';
|
||||
|
||||
import { restart } from './providers/app-controls';
|
||||
import registerCallback from './providers/song-info';
|
||||
import getSongControls from './providers/song-controls';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
@ -46,14 +48,18 @@ export const setUpTray = (app: Electron.App, win: Electron.BrowserWindow) => {
|
||||
|
||||
const { playPause, next, previous } = getSongControls(win);
|
||||
|
||||
const trayIcon = nativeImage.createFromPath(youtubeMusicTrayIcon).resize({
|
||||
const defaultTrayIcon = nativeImage.createFromPath(defaultTrayIconAsset).resize({
|
||||
width: 16,
|
||||
height: 16,
|
||||
});
|
||||
const pausedTrayIcon = nativeImage.createFromPath(pausedTrayIconAsset).resize({
|
||||
width: 16,
|
||||
height: 16,
|
||||
});
|
||||
|
||||
tray = new Tray(trayIcon);
|
||||
tray = new Tray(defaultTrayIcon);
|
||||
|
||||
tray.setToolTip('YouTube Music');
|
||||
tray.setToolTip(t('main.tray.tooltip.default'));
|
||||
|
||||
// MacOS only
|
||||
tray.setIgnoreDoubleClickEvents(true);
|
||||
@ -110,4 +116,18 @@ export const setUpTray = (app: Electron.App, win: Electron.BrowserWindow) => {
|
||||
|
||||
const trayMenu = Menu.buildFromTemplate(template);
|
||||
tray.setContextMenu(trayMenu);
|
||||
|
||||
registerCallback(songInfo => {
|
||||
if (typeof songInfo.isPaused === 'undefined') {
|
||||
tray.setImage(defaultTrayIcon);
|
||||
return;
|
||||
}
|
||||
|
||||
tray.setToolTip(t('main.tray.tooltip.with-song-info', {
|
||||
artist: songInfo.artist,
|
||||
title: songInfo.title,
|
||||
}));
|
||||
|
||||
tray.setImage(songInfo.isPaused ? pausedTrayIcon : defaultTrayIcon);
|
||||
})
|
||||
};
|
||||
|
||||
@ -256,6 +256,7 @@ export type VideoDataChangeValue = Record<string, unknown> & {
|
||||
export interface PlayerAPIEvents {
|
||||
videodatachange: {
|
||||
value: VideoDataChangeValue;
|
||||
} & ({ name: 'dataloaded' } | { name: 'dataupdated ' });
|
||||
name: 'dataloaded' | 'dataupdated';
|
||||
};
|
||||
onStateChange: number;
|
||||
}
|
||||
|
||||
@ -357,8 +357,8 @@ export interface YoutubePlayer {
|
||||
type: K,
|
||||
listener: (
|
||||
this: Document,
|
||||
name: PlayerAPIEvents[K]['name'],
|
||||
data: PlayerAPIEvents[K]['value'],
|
||||
name: K extends 'videodatachange' ? PlayerAPIEvents[K]['name'] : never,
|
||||
data: K extends 'videodatachange' ? PlayerAPIEvents[K]['value'] : never,
|
||||
) => void,
|
||||
options?: boolean | AddEventListenerOptions | undefined,
|
||||
) => void;
|
||||
@ -366,8 +366,8 @@ export interface YoutubePlayer {
|
||||
type: K,
|
||||
listener: (
|
||||
this: Document,
|
||||
name: PlayerAPIEvents[K]['name'],
|
||||
data: PlayerAPIEvents[K]['value'],
|
||||
name: K extends 'videodatachange' ? PlayerAPIEvents[K]['name'] : never,
|
||||
data: K extends 'videodatachange' ? PlayerAPIEvents[K]['value'] : never,
|
||||
) => void,
|
||||
options?: boolean | EventListenerOptions | undefined,
|
||||
) => void;
|
||||
|
||||
Reference in New Issue
Block a user