mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 18:41:47 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e66b0cedd | |||
| 11fe54d640 | |||
| 446529f738 | |||
| ac6e9deeb9 | |||
| 100873163f | |||
| e141e18bac | |||
| f4da0c2c95 | |||
| 7507bce3cc | |||
| 2b970fade8 | |||
| 35f0e43082 | |||
| ae4410a613 | |||
| 5534174016 | |||
| 15cf6c77c3 | |||
| 18a8fc462d | |||
| d3acb4945a | |||
| 32d3c58b44 | |||
| bd8c2eb390 | |||
| a81fa9c0d1 | |||
| 1d0f7d7a48 | |||
| b6687307df | |||
| 7e07a44f68 | |||
| 5ca66530ee | |||
| 95acbe2b65 | |||
| 534aeb163a | |||
| 6abcbee290 | |||
| 4457a043a4 | |||
| 410a052fea | |||
| e4287085a1 | |||
| b6cefef8fb | |||
| 9d7e2a06bc | |||
| d516fc2153 | |||
| 77bfe8e218 | |||
| 0fcbe38837 | |||
| b85a40f683 |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -62,6 +62,12 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
sudo snap install snapcraft --classic
|
||||
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
|
||||
pnpm release:linux
|
||||
|
||||
- name: Build and release on Windows
|
||||
|
||||
48
changelog.md
48
changelog.md
@ -2,8 +2,56 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
|
||||
|
||||
#### [v3.6.0](https://github.com/th-ch/youtube-music/compare/v3.5.3...v3.6.0)
|
||||
|
||||
- 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)
|
||||
- chore(deps): update dependency typescript to v5.6.3 [`#2486`](https://github.com/th-ch/youtube-music/pull/2486)
|
||||
- chore(deps): update dependency electron to v32.2.0 [`#2487`](https://github.com/th-ch/youtube-music/pull/2487)
|
||||
- chore(deps): update dependency del-cli to v6 [`#2475`](https://github.com/th-ch/youtube-music/pull/2475)
|
||||
- chore(deps): update dependency typescript-eslint to v8.8.1 [`#2477`](https://github.com/th-ch/youtube-music/pull/2477)
|
||||
- fix(deps): update dependency solid-js to v1.9.2 [`#2480`](https://github.com/th-ch/youtube-music/pull/2480)
|
||||
- Revert "chore(deps): update dependency electron-builder to v25" [`#2488`](https://github.com/th-ch/youtube-music/pull/2488)
|
||||
- chore(deps): update dependency electron-builder to v25 [`#2406`](https://github.com/th-ch/youtube-music/pull/2406)
|
||||
- fix(deps): update dependency deepmerge-ts to v7.1.3 [`#2481`](https://github.com/th-ch/youtube-music/pull/2481)
|
||||
- fix(deps): update dependency ts-morph to v24 [`#2474`](https://github.com/th-ch/youtube-music/pull/2474)
|
||||
- fix(deps): update dependency i18next to v23.15.2 [`#2471`](https://github.com/th-ch/youtube-music/pull/2471)
|
||||
- chore(deps): update eslint monorepo to v9.12.0 [`#2470`](https://github.com/th-ch/youtube-music/pull/2470)
|
||||
- chore(deps): update dependency @stylistic/eslint-plugin-js to v2.9.0 [`#2469`](https://github.com/th-ch/youtube-music/pull/2469)
|
||||
- chore(deps): bump micromatch from 4.0.5 to 4.0.8 [`#2465`](https://github.com/th-ch/youtube-music/pull/2465)
|
||||
- chore(deps): bump braces from 3.0.2 to 3.0.3 [`#2466`](https://github.com/th-ch/youtube-music/pull/2466)
|
||||
- fix(deps): update dependency electron-updater to v6.3.9 [`#2468`](https://github.com/th-ch/youtube-music/pull/2468)
|
||||
- fix(deps): update dependency deepmerge-ts to v7.1.1 [`#2467`](https://github.com/th-ch/youtube-music/pull/2467)
|
||||
- chore(deps): update dependency typescript-eslint to v8.8.0 [`#2457`](https://github.com/th-ch/youtube-music/pull/2457)
|
||||
- chore(deps): update dependency @babel/runtime to v7.25.7 [`#2462`](https://github.com/th-ch/youtube-music/pull/2462)
|
||||
- chore(deps): update dependency rollup to v4.24.0 [`#2458`](https://github.com/th-ch/youtube-music/pull/2458)
|
||||
- chore(deps): update dependency eslint-plugin-import to v2.31.0 [`#2464`](https://github.com/th-ch/youtube-music/pull/2464)
|
||||
- chore(deps): update dependency rollup to v4.22.5 [`#2448`](https://github.com/th-ch/youtube-music/pull/2448)
|
||||
- chore(deps): update dependency typescript-eslint to v8.7.0 [`#2450`](https://github.com/th-ch/youtube-music/pull/2450)
|
||||
- fix(deps): update dependency solid-js to v1.9.1 [`#2451`](https://github.com/th-ch/youtube-music/pull/2451)
|
||||
- chore(deps): update dependency vite to v5.4.8 [`#2449`](https://github.com/th-ch/youtube-music/pull/2449)
|
||||
- chore(deps): update dependency discord-api-types to v0.37.101 [`#2440`](https://github.com/th-ch/youtube-music/pull/2440)
|
||||
- chore(deps): update dependency esbuild to v0.24.0 [`#2439`](https://github.com/th-ch/youtube-music/pull/2439)
|
||||
- chore(deps): update eslint monorepo to v9.11.1 [`#2442`](https://github.com/th-ch/youtube-music/pull/2442)
|
||||
- chore(deps): update dependency @types/howler to v2.2.12 [`#2443`](https://github.com/th-ch/youtube-music/pull/2443)
|
||||
- chore(deps): update dependency vite to v5.4.7 [`#2434`](https://github.com/th-ch/youtube-music/pull/2434)
|
||||
- chore(deps): update playwright monorepo to v1.47.2 [`#2436`](https://github.com/th-ch/youtube-music/pull/2436)
|
||||
- chore(deps): update eslint monorepo to v9.11.0 [`#2437`](https://github.com/th-ch/youtube-music/pull/2437)
|
||||
- fix(deps): update dependency youtubei.js to v10.5.0 [`#2431`](https://github.com/th-ch/youtube-music/pull/2431)
|
||||
- chore(deps): update dependency rollup to v4.22.4 [`#2430`](https://github.com/th-ch/youtube-music/pull/2430)
|
||||
- chore(deps): update dependency electron to v32.1.2 [`#2433`](https://github.com/th-ch/youtube-music/pull/2433)
|
||||
- feat: ESLint Flat Config (v9 support #2229) [`#2426`](https://github.com/th-ch/youtube-music/pull/2426)
|
||||
- fix(taskbar-mediacontrol): fix icon color [`#2485`](https://github.com/th-ch/youtube-music/issues/2485)
|
||||
- chore(eslint): apply eslint-plugin-prettier [`#2438`](https://github.com/th-ch/youtube-music/issues/2438)
|
||||
- fix: apply fix from eslint [`cb1381b`](https://github.com/th-ch/youtube-music/commit/cb1381bbb394e2bbb404f44817ef96411dabc8a9)
|
||||
- chore(i18n): Translated using Weblate (Portuguese (Brazil)) [`bcff26c`](https://github.com/th-ch/youtube-music/commit/bcff26c85b18258806f3960309776bc860c3a54e)
|
||||
- chore(i18n): Translated using Weblate (Persian) [`ead448e`](https://github.com/th-ch/youtube-music/commit/ead448ed98095339557903eb0f84c4a6d0f32058)
|
||||
|
||||
#### [v3.5.3](https://github.com/th-ch/youtube-music/compare/v3.5.2...v3.5.3)
|
||||
|
||||
> 17 September 2024
|
||||
|
||||
- fix: fix `trustedHTML` issue [`#2339`](https://github.com/th-ch/youtube-music/issues/2339)
|
||||
- chore(deps): update dependency rollup to v4.21.3 [`6edc84a`](https://github.com/th-ch/youtube-music/commit/6edc84a8bd6c7e009041117ba0d2004783eb3a47)
|
||||
- chore(deps): update typescript-eslint monorepo to v8.6.0 [`d4c8a43`](https://github.com/th-ch/youtube-music/commit/d4c8a4320d733f7bddc4dcd1de93644790e71d66)
|
||||
|
||||
81
package.json
81
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "youtube-music",
|
||||
"productName": "YouTube Music",
|
||||
"version": "3.6.0",
|
||||
"version": "3.6.1",
|
||||
"description": "YouTube Music Desktop App - including custom plugins",
|
||||
"main": "./dist/main/index.js",
|
||||
"license": "MIT",
|
||||
@ -40,7 +40,8 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"icon": "assets/generated/icons/mac/icon.icns"
|
||||
"icon": "assets/generated/icons/mac/icon.icns",
|
||||
"compression": "maximum"
|
||||
},
|
||||
"win": {
|
||||
"icon": "assets/generated/icons/win/icon.ico",
|
||||
@ -61,7 +62,8 @@
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"compression": "maximum"
|
||||
},
|
||||
"nsisWeb": {
|
||||
"runAfterFinish": false
|
||||
@ -70,13 +72,67 @@
|
||||
"icon": "assets/generated/icons/png",
|
||||
"category": "AudioVideo",
|
||||
"target": [
|
||||
"AppImage",
|
||||
"snap",
|
||||
"freebsd",
|
||||
"deb",
|
||||
"rpm"
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
"armv7l"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "flatpak",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "deb",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
"armv7l"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "rpm",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "snap",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "freebsd",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
"armv7l"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "tar.gz",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
"armv7l"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"appImage": {
|
||||
"description": "YouTube Music Desktop App bundled with custom plugins (and built-in ad blocker / downloader)",
|
||||
"category": "AudioVideo"
|
||||
},
|
||||
"flatpak": {
|
||||
"description": "YouTube Music Desktop App bundled with custom plugins (and built-in ad blocker / downloader)",
|
||||
"category": "AudioVideo"
|
||||
},
|
||||
"deb": {
|
||||
"depends": [
|
||||
"libgtk-3-0",
|
||||
@ -139,7 +195,7 @@
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"node": ">=18",
|
||||
"pnpm": ">=8"
|
||||
},
|
||||
"pnpm": {
|
||||
@ -153,7 +209,8 @@
|
||||
},
|
||||
"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@24.13.3": "patches/app-builder-lib@24.13.3.patch",
|
||||
"@malept/flatpak-bundler": "patches/@malept__flatpak-bundler.patch"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@ -182,7 +239,7 @@
|
||||
"custom-electron-prompt": "1.5.8",
|
||||
"dbus-next": "0.10.2",
|
||||
"deepmerge-ts": "7.1.3",
|
||||
"electron-debug": "4.0.1",
|
||||
"electron-debug": "4.1.0",
|
||||
"electron-is": "3.0.0",
|
||||
"electron-localshortcut": "3.2.1",
|
||||
"electron-store": "10.0.0",
|
||||
@ -194,7 +251,7 @@
|
||||
"hono": "4.6.4",
|
||||
"howler": "2.2.4",
|
||||
"html-to-text": "9.0.5",
|
||||
"i18next": "23.15.2",
|
||||
"i18next": "23.16.0",
|
||||
"jimp": "1.6.0",
|
||||
"keyboardevent-from-electron-accelerator": "2.0.0",
|
||||
"keyboardevents-areequal": "0.2.2",
|
||||
|
||||
29
patches/@malept__flatpak-bundler.patch
Normal file
29
patches/@malept__flatpak-bundler.patch
Normal file
@ -0,0 +1,29 @@
|
||||
diff --git a/index.js b/index.js
|
||||
index 5968fcf47b69094993b0f861c03f5560e4a6a9b7..0fe16d4f40612c0abfa57898909ce0083f56944c 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -56,19 +56,23 @@ function getOptionsWithDefaults (options, manifest) {
|
||||
async function spawnWithLogging (options, command, args, allowFail) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger(`$ ${command} ${args.join(' ')}`)
|
||||
+ const output = []
|
||||
const child = childProcess.spawn(command, args, { cwd: options['working-dir'] })
|
||||
child.stdout.on('data', (data) => {
|
||||
+ output.push(data)
|
||||
logger(`1> ${data}`)
|
||||
})
|
||||
child.stderr.on('data', (data) => {
|
||||
+ output.push(data)
|
||||
logger(`2> ${data}`)
|
||||
})
|
||||
child.on('error', (error) => {
|
||||
+ logger(`error - ${error.message} ${error.stack}`)
|
||||
reject(error)
|
||||
})
|
||||
child.on('close', (code) => {
|
||||
if (!allowFail && code !== 0) {
|
||||
- reject(new Error(`${command} failed with status code ${code}`))
|
||||
+ reject(new Error(`${command} ${args.join(' ')} failed with status code ${code} ${output.join(' ')}`))
|
||||
}
|
||||
resolve(code === 0)
|
||||
})
|
||||
27
pnpm-lock.yaml
generated
27
pnpm-lock.yaml
generated
@ -13,6 +13,9 @@ overrides:
|
||||
'@babel/runtime': 7.25.7
|
||||
|
||||
patchedDependencies:
|
||||
'@malept/flatpak-bundler':
|
||||
hash: vli4xtu6rndz3xtsscixhngsxa
|
||||
path: patches/@malept__flatpak-bundler.patch
|
||||
app-builder-lib@24.13.3:
|
||||
hash: zcnm2qnjaggm2keyecnhiglkke
|
||||
path: patches/app-builder-lib@24.13.3.patch
|
||||
@ -100,8 +103,8 @@ importers:
|
||||
specifier: 7.1.3
|
||||
version: 7.1.3
|
||||
electron-debug:
|
||||
specifier: 4.0.1
|
||||
version: 4.0.1
|
||||
specifier: 4.1.0
|
||||
version: 4.1.0
|
||||
electron-is:
|
||||
specifier: 3.0.0
|
||||
version: 3.0.0
|
||||
@ -136,8 +139,8 @@ importers:
|
||||
specifier: 9.0.5
|
||||
version: 9.0.5
|
||||
i18next:
|
||||
specifier: 23.15.2
|
||||
version: 23.15.2
|
||||
specifier: 23.16.0
|
||||
version: 23.16.0
|
||||
jimp:
|
||||
specifier: 1.6.0
|
||||
version: 1.6.0
|
||||
@ -2095,8 +2098,8 @@ packages:
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
|
||||
electron-debug@4.0.1:
|
||||
resolution: {integrity: sha512-PdUG3SvcK70P05z99PFLUzn0+lPZl5c4quG1bXI7OtPaXxidwh8UONcdRLsr+6J9kf5y1FycJD5nBd80dYrcsA==}
|
||||
electron-debug@4.1.0:
|
||||
resolution: {integrity: sha512-rdbvmotqbaNcSuinPe1tzB5zK+JKal+4LSDbguBcqTLARNqWrGoRS/TkR1gGH4+63boYH3HUaf9r9ECAxgIe9g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
electron-devtools-installer@3.2.0:
|
||||
@ -2717,8 +2720,8 @@ packages:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
|
||||
i18next@23.15.2:
|
||||
resolution: {integrity: sha512-zcPSWzCvw6uKnuYHIqs4W7hTuB9e3AFcSdZgvCWoPXIZsBjBd4djN2/2uOHIB+1DFFkQnMBXvhNg7J3WyCuywQ==}
|
||||
i18next@23.16.0:
|
||||
resolution: {integrity: sha512-Ni3CG6c14teOogY19YNRl+kYaE/Rb59khy0VyHVn4uOZ97E2E/Yziyi6r3C3s9+wacjdLZiq/LLYyx+Cgd+FCw==}
|
||||
|
||||
iconv-corefoundation@1.1.7:
|
||||
resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==}
|
||||
@ -5135,7 +5138,7 @@ snapshots:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.3
|
||||
|
||||
'@malept/flatpak-bundler@0.4.0':
|
||||
'@malept/flatpak-bundler@0.4.0(patch_hash=vli4xtu6rndz3xtsscixhngsxa)':
|
||||
dependencies:
|
||||
debug: 4.3.7
|
||||
fs-extra: 9.1.0
|
||||
@ -5618,7 +5621,7 @@ snapshots:
|
||||
'@electron/notarize': 2.2.1
|
||||
'@electron/osx-sign': 1.0.5
|
||||
'@electron/universal': 2.0.1
|
||||
'@malept/flatpak-bundler': 0.4.0
|
||||
'@malept/flatpak-bundler': 0.4.0(patch_hash=vli4xtu6rndz3xtsscixhngsxa)
|
||||
'@types/fs-extra': 9.0.13
|
||||
async-exit-hook: 2.0.1
|
||||
bluebird-lst: 1.0.9
|
||||
@ -6351,7 +6354,7 @@ snapshots:
|
||||
- electron-builder-squirrel-windows
|
||||
- supports-color
|
||||
|
||||
electron-debug@4.0.1:
|
||||
electron-debug@4.1.0:
|
||||
dependencies:
|
||||
electron-is-dev: 3.0.1
|
||||
electron-localshortcut: 3.2.1
|
||||
@ -7190,7 +7193,7 @@ snapshots:
|
||||
|
||||
human-signals@2.1.0: {}
|
||||
|
||||
i18next@23.15.2:
|
||||
i18next@23.16.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.25.7
|
||||
|
||||
|
||||
@ -281,24 +281,17 @@
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Adds an API server to control the player",
|
||||
"name": "API Server [Beta]",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"title": "API authorization request",
|
||||
"message": "Allow {{ID}} ({{origin}}) to access the API?",
|
||||
"buttons": {
|
||||
"allow": "Allow",
|
||||
"deny": "Deny"
|
||||
}
|
||||
},
|
||||
"message": "Allow {{ID}} ({{origin}}) to access the API?",
|
||||
"title": "API authorization request"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"hostname": {
|
||||
"label": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port"
|
||||
},
|
||||
"auth-strategy": {
|
||||
"label": "Authorization strategy",
|
||||
"submenu": {
|
||||
@ -309,16 +302,23 @@
|
||||
"label": "No authorization"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
},
|
||||
"hostname": {
|
||||
"title": "Hostname",
|
||||
"label": "Enter the hostname (like 0.0.0.0) for the API server:"
|
||||
"label": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"title": "Port",
|
||||
"label": "Enter the port for the API server:"
|
||||
"label": "Port"
|
||||
}
|
||||
},
|
||||
"name": "API Server [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Enter the hostname (like 0.0.0.0) for the API server:",
|
||||
"title": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"label": "Enter the port for the API server:",
|
||||
"title": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -293,6 +293,7 @@
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Estrategia de autorización",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Autorizar la primera solicitud"
|
||||
|
||||
@ -201,6 +201,7 @@
|
||||
"restart": "I-restart ang App",
|
||||
"show": "Ipakita ang window",
|
||||
"tooltip": {
|
||||
"default": "YouTube Music",
|
||||
"with-song-info": "YouTube Music: {{artist}} - {{title}}"
|
||||
}
|
||||
}
|
||||
@ -212,6 +213,9 @@
|
||||
},
|
||||
"adblocker": {
|
||||
"description": "I-block ang lahat ng ad at tracking",
|
||||
"menu": {
|
||||
"blocker": "Blocker"
|
||||
},
|
||||
"name": "Pag-block ng Ad"
|
||||
},
|
||||
"album-actions": {
|
||||
@ -222,7 +226,10 @@
|
||||
"description": "Naglalapat ng dynamic na tema at visual effect batay sa color palette ng album",
|
||||
"menu": {
|
||||
"color-mix-ratio": {
|
||||
"label": "Ratio ng paghahalo ng kulay"
|
||||
"label": "Ratio ng paghahalo ng kulay",
|
||||
"submenu": {
|
||||
"percent": "{{ratio}}%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Tema ng Kulay ng Album"
|
||||
@ -261,6 +268,7 @@
|
||||
}
|
||||
},
|
||||
"smoothness-transition": {
|
||||
"label": "Ayos ng Transisyon",
|
||||
"submenu": {
|
||||
"during": "Habang {{interpolationTime}} s"
|
||||
}
|
||||
@ -268,6 +276,50 @@
|
||||
"use-fullscreen": {
|
||||
"label": "Gumamit ng fullscreen"
|
||||
}
|
||||
},
|
||||
"name": "Ambient Mode"
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Nagdadagdag ng API Server upang kontrolin ang player",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Payagan",
|
||||
"deny": "Tanggihan"
|
||||
},
|
||||
"message": "Payagan ang {{ID}} ({{origin}}) upang ma-access ang API?",
|
||||
"title": "Awtorisasyon ng API request"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Estratehiya ng awtorisasyon",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Mag-autorisa sa unang request"
|
||||
},
|
||||
"none": {
|
||||
"label": "Walang awtorisasyon"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port"
|
||||
}
|
||||
},
|
||||
"name": "API Server [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Itala ang hostname (tulad ng 0.0.0.0) para sa API server:",
|
||||
"title": "Hostname"
|
||||
},
|
||||
"port": {
|
||||
"label": "Itala ang port para sa API server:",
|
||||
"title": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
@ -301,7 +353,8 @@
|
||||
}
|
||||
},
|
||||
"compact-sidebar": {
|
||||
"description": "Laging i-set ang sidebar sa compact mode"
|
||||
"description": "Laging i-set ang sidebar sa compact mode",
|
||||
"name": "Pinaliit na Sidebar"
|
||||
},
|
||||
"crossfade": {
|
||||
"description": "I-crossfade kada kanta",
|
||||
@ -425,7 +478,8 @@
|
||||
}
|
||||
},
|
||||
"lumiastream": {
|
||||
"description": "Nabibigay suporta sa Lumia Stream"
|
||||
"description": "Nabibigay suporta sa Lumia Stream",
|
||||
"name": "Lumia Stream [Beta]"
|
||||
},
|
||||
"lyrics-genius": {
|
||||
"description": "Nagdaragdag ng suporta sa lyrics para sa karamihan ng kanta",
|
||||
@ -582,7 +636,8 @@
|
||||
},
|
||||
"listenbrainz": {
|
||||
"token": {
|
||||
"label": "Ilagay ang ListenBrainz user token:"
|
||||
"label": "Ilagay ang ListenBrainz user token:",
|
||||
"title": "Token ng ListenBrainz"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -696,7 +751,10 @@
|
||||
}
|
||||
},
|
||||
"visualizer": {
|
||||
"description": "Idaragdag ng visualizer sa player"
|
||||
"description": "Idaragdag ng visualizer sa player",
|
||||
"menu": {
|
||||
"visualizer-type": "Uri ng Visualizer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,6 +279,49 @@
|
||||
},
|
||||
"name": "Modo ambiente"
|
||||
},
|
||||
"api-server": {
|
||||
"description": "Adiciona um servidor API para controlar o player",
|
||||
"dialog": {
|
||||
"request": {
|
||||
"buttons": {
|
||||
"allow": "Permitir",
|
||||
"deny": "Negar"
|
||||
},
|
||||
"message": "Permitir que {{ID}} {{origin}} acesse o API?",
|
||||
"title": "Pedido de autorização API"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"auth-strategy": {
|
||||
"label": "Estratégia de autorização",
|
||||
"submenu": {
|
||||
"auth-at-first": {
|
||||
"label": "Autorizar na primeira solicitação"
|
||||
},
|
||||
"none": {
|
||||
"label": "Não autorizar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"label": "Nome do anfitrião"
|
||||
},
|
||||
"port": {
|
||||
"label": "Porta"
|
||||
}
|
||||
},
|
||||
"name": "Servidor API [Beta]",
|
||||
"prompt": {
|
||||
"hostname": {
|
||||
"label": "Entre o nome do host (como 0.0.0.0) para o servidor API:",
|
||||
"title": "Nome do anfitrião"
|
||||
},
|
||||
"port": {
|
||||
"label": "Entre a porta do servidor API:",
|
||||
"title": "Porta"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio-compressor": {
|
||||
"description": "Aplicar compressão ao áudio (reduz o volume das partes mais altas e aumenta o volume das partes mais baixas)",
|
||||
"name": "Compressor de áudio"
|
||||
|
||||
35
src/index.ts
35
src/index.ts
@ -11,6 +11,7 @@ import {
|
||||
shell,
|
||||
dialog,
|
||||
ipcMain,
|
||||
protocol,
|
||||
type BrowserWindowConstructorOptions,
|
||||
} from 'electron';
|
||||
import enhanceWebRequest, {
|
||||
@ -83,6 +84,34 @@ if (!gotTheLock) {
|
||||
app.exit();
|
||||
}
|
||||
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
scheme: 'http',
|
||||
privileges: {
|
||||
standard: true,
|
||||
bypassCSP: true,
|
||||
allowServiceWorkers: true,
|
||||
supportFetchAPI: true,
|
||||
corsEnabled: true,
|
||||
stream: true,
|
||||
codeCache: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
scheme: 'https',
|
||||
privileges: {
|
||||
standard: true,
|
||||
bypassCSP: true,
|
||||
allowServiceWorkers: true,
|
||||
supportFetchAPI: true,
|
||||
corsEnabled: true,
|
||||
stream: true,
|
||||
codeCache: true,
|
||||
},
|
||||
},
|
||||
{ scheme: 'mailto', privileges: { standard: true } },
|
||||
]);
|
||||
|
||||
// Ozone platform hint: Required for Wayland support
|
||||
app.commandLine.appendSwitch('ozone-platform-hint', 'auto');
|
||||
// SharedArrayBuffer: Required for downloader (@ffmpeg/core-mt)
|
||||
@ -531,7 +560,11 @@ app.once('browser-window-created', (_event, win) => {
|
||||
console.log(log);
|
||||
}
|
||||
|
||||
if (errorCode !== -3) {
|
||||
if (
|
||||
errorCode !== -3 &&
|
||||
// Workaround for #2435
|
||||
!new URL(validatedURL).hostname.includes('doubleclick.net')
|
||||
) {
|
||||
// -3 is a false positive
|
||||
win.webContents.send('log', log);
|
||||
win.webContents.loadFile(ErrorHtmlAsset);
|
||||
|
||||
@ -10,14 +10,15 @@ import { createBackend } from '@/utils';
|
||||
import { JWTPayloadSchema } from './scheme';
|
||||
import { registerAuth, registerControl } from './routes';
|
||||
|
||||
import type { APIServerConfig } from '../config';
|
||||
import { type APIServerConfig, AuthStrategy } from '../config';
|
||||
|
||||
import type { BackendType } from './types';
|
||||
|
||||
export const backend = createBackend<BackendType, APIServerConfig>({
|
||||
async start(ctx) {
|
||||
const config = await ctx.getConfig();
|
||||
|
||||
this.init(ctx);
|
||||
await this.init(ctx);
|
||||
registerCallback((songInfo) => {
|
||||
this.songInfo = songInfo;
|
||||
});
|
||||
@ -49,17 +50,20 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
||||
this.app.use('*', cors());
|
||||
|
||||
// middlewares
|
||||
this.app.use(
|
||||
'/api/*',
|
||||
jwt({
|
||||
secret: config.secret,
|
||||
}),
|
||||
);
|
||||
this.app.use('/api/*', async (ctx, next) => {
|
||||
if (config.authStrategy !== AuthStrategy.NONE) {
|
||||
return await jwt({
|
||||
secret: config.secret,
|
||||
})(ctx, next);
|
||||
}
|
||||
await next();
|
||||
});
|
||||
this.app.use('/api/*', async (ctx, next) => {
|
||||
const result = await JWTPayloadSchema.spa(await ctx.get('jwtPayload'));
|
||||
|
||||
const isAuthorized =
|
||||
result.success && config.authorizedClients.includes(result.data.id);
|
||||
config.authStrategy === AuthStrategy.NONE ||
|
||||
(result.success && config.authorizedClients.includes(result.data.id));
|
||||
if (!isAuthorized) {
|
||||
ctx.status(401);
|
||||
return ctx.body('Unauthorized');
|
||||
@ -73,12 +77,26 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
||||
registerAuth(this.app, ctx);
|
||||
|
||||
// swagger
|
||||
this.app.openAPIRegistry.registerComponent(
|
||||
'securitySchemes',
|
||||
'bearerAuth',
|
||||
{
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: 'JWT',
|
||||
},
|
||||
);
|
||||
this.app.doc('/doc', {
|
||||
openapi: '3.1.0',
|
||||
info: {
|
||||
version: '1.0.0',
|
||||
title: 'Youtube Music API Server',
|
||||
},
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
this.app.get('/swagger', swaggerUI({ url: '/doc' }));
|
||||
|
||||
@ -6,9 +6,9 @@ import { getConnInfo } from '@hono/node-server/conninfo';
|
||||
|
||||
import { t } from '@/i18n';
|
||||
|
||||
import { APIServerConfig } from '../../config';
|
||||
import { JWTPayload } from '../scheme';
|
||||
import { type APIServerConfig, AuthStrategy } from '../../config';
|
||||
|
||||
import type { JWTPayload } from '../scheme';
|
||||
import type { HonoApp } from '../types';
|
||||
import type { BackendContext } from '@/types/contexts';
|
||||
|
||||
@ -18,6 +18,7 @@ const routes = {
|
||||
path: '/auth/{id}',
|
||||
summary: '',
|
||||
description: '',
|
||||
security: [],
|
||||
request: {
|
||||
params: z.object({
|
||||
id: z.string(),
|
||||
@ -51,16 +52,16 @@ export const register = (
|
||||
|
||||
if (config.authorizedClients.includes(id)) {
|
||||
// SKIP CHECK
|
||||
} else if (config.authStrategy === 'AUTH_AT_FIRST') {
|
||||
} else if (config.authStrategy === AuthStrategy.AUTH_AT_FIRST) {
|
||||
const result = await dialog.showMessageBox({
|
||||
title: t('plugins.api-server.dialog.request.title'),
|
||||
message: t('plugins.api-server.dialog.request.message', {
|
||||
origin: getConnInfo(ctx).remote.address,
|
||||
id,
|
||||
ID: id,
|
||||
}),
|
||||
buttons: [
|
||||
t('plugins.api-server.dialog.request.buttons.allow'),
|
||||
t('plugins.api-server.dialog.request.deny'),
|
||||
t('plugins.api-server.dialog.request.buttons.deny'),
|
||||
],
|
||||
defaultId: 1,
|
||||
cancelId: 1,
|
||||
@ -70,7 +71,7 @@ export const register = (
|
||||
ctx.status(403);
|
||||
return ctx.body(null);
|
||||
}
|
||||
} else if (config.authStrategy === 'NONE') {
|
||||
} else if (config.authStrategy === AuthStrategy.NONE) {
|
||||
// SKIP CHECK
|
||||
}
|
||||
|
||||
|
||||
@ -29,9 +29,6 @@ const routes = {
|
||||
path: `/api/${API_VERSION}/previous`,
|
||||
summary: 'play previous song',
|
||||
description: 'Plays the previous song in the queue',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
@ -43,9 +40,6 @@ const routes = {
|
||||
path: `/api/${API_VERSION}/next`,
|
||||
summary: 'play next song',
|
||||
description: 'Plays the next song in the queue',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
@ -57,9 +51,6 @@ const routes = {
|
||||
path: `/api/${API_VERSION}/play`,
|
||||
summary: 'Play',
|
||||
description: 'Change the state of the player to play',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
@ -71,9 +62,6 @@ const routes = {
|
||||
path: `/api/${API_VERSION}/pause`,
|
||||
summary: 'Pause',
|
||||
description: 'Change the state of the player to pause',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
@ -86,9 +74,6 @@ const routes = {
|
||||
summary: 'Toggle play/pause',
|
||||
description:
|
||||
'Change the state of the player to play if paused, or pause if playing',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
@ -100,9 +85,6 @@ const routes = {
|
||||
path: `/api/${API_VERSION}/like`,
|
||||
summary: 'like song',
|
||||
description: 'Set the current song as liked',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
@ -114,9 +96,6 @@ const routes = {
|
||||
path: `/api/${API_VERSION}/dislike`,
|
||||
summary: 'dislike song',
|
||||
description: 'Set the current song as disliked',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
@ -175,9 +154,6 @@ const routes = {
|
||||
path: `/api/${API_VERSION}/shuffle`,
|
||||
summary: 'shuffle',
|
||||
description: 'Shuffle the queue',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
@ -255,9 +231,6 @@ const routes = {
|
||||
path: `/api/${API_VERSION}/toggle-mute`,
|
||||
summary: 'toggle mute',
|
||||
description: 'Toggle the mute state of the player',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
},
|
||||
responses: {
|
||||
204: {
|
||||
description: 'Success',
|
||||
@ -270,9 +243,6 @@ const routes = {
|
||||
path: `/api/${API_VERSION}/fullscreen`,
|
||||
summary: 'get fullscreen state',
|
||||
description: 'Get the current fullscreen state',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Success',
|
||||
@ -291,9 +261,6 @@ const routes = {
|
||||
path: `/api/${API_VERSION}/queue-info`,
|
||||
summary: 'get current queue info',
|
||||
description: 'Get the current queue info',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Success',
|
||||
@ -313,9 +280,6 @@ const routes = {
|
||||
path: `/api/${API_VERSION}/song-info`,
|
||||
summary: 'get current song info',
|
||||
description: 'Get the current song info',
|
||||
request: {
|
||||
headers: AuthHeadersSchema,
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Success',
|
||||
|
||||
@ -12,7 +12,7 @@ export type BackendType = {
|
||||
oldConfig?: APIServerConfig;
|
||||
songInfo?: SongInfo;
|
||||
|
||||
init: (ctx: BackendContext<APIServerConfig>) => void;
|
||||
init: (ctx: BackendContext<APIServerConfig>) => Promise<void>;
|
||||
run: (hostname: string, port: number) => void;
|
||||
end: () => void;
|
||||
};
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
export enum AuthStrategy {
|
||||
AUTH_AT_FIRST = 'AUTH_AT_FIRST',
|
||||
NONE = 'NONE',
|
||||
}
|
||||
|
||||
export interface APIServerConfig {
|
||||
enabled: boolean;
|
||||
hostname: string;
|
||||
port: number;
|
||||
authStrategy: 'AUTH_AT_FIRST' | 'NONE';
|
||||
authStrategy: AuthStrategy;
|
||||
secret: string;
|
||||
|
||||
authorizedClients: string[];
|
||||
@ -12,7 +17,7 @@ export const defaultAPIServerConfig: APIServerConfig = {
|
||||
enabled: true,
|
||||
hostname: '0.0.0.0',
|
||||
port: 26538,
|
||||
authStrategy: 'AUTH_AT_FIRST',
|
||||
authStrategy: AuthStrategy.AUTH_AT_FIRST,
|
||||
secret: Date.now().toString(36),
|
||||
|
||||
authorizedClients: [],
|
||||
|
||||
@ -3,7 +3,11 @@ import prompt from 'custom-electron-prompt';
|
||||
import { t } from '@/i18n';
|
||||
import promptOptions from '@/providers/prompt-options';
|
||||
|
||||
import { APIServerConfig, defaultAPIServerConfig } from './config';
|
||||
import {
|
||||
type APIServerConfig,
|
||||
AuthStrategy,
|
||||
defaultAPIServerConfig,
|
||||
} from './config';
|
||||
|
||||
import type { MenuContext } from '@/types/contexts';
|
||||
import type { MenuTemplate } from '@/menu';
|
||||
@ -74,17 +78,17 @@ export const onMenu = async ({
|
||||
'plugins.api-server.menu.auth-strategy.submenu.auth-at-first.label',
|
||||
),
|
||||
type: 'radio',
|
||||
checked: config.authStrategy === 'AUTH_AT_FIRST',
|
||||
checked: config.authStrategy === AuthStrategy.AUTH_AT_FIRST,
|
||||
click() {
|
||||
setConfig({ ...config, authStrategy: 'AUTH_AT_FIRST' });
|
||||
setConfig({ ...config, authStrategy: AuthStrategy.AUTH_AT_FIRST });
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('plugins.api-server.menu.auth-strategy.submenu.none.label'),
|
||||
type: 'radio',
|
||||
checked: config.authStrategy === 'NONE',
|
||||
checked: config.authStrategy === AuthStrategy.NONE,
|
||||
click() {
|
||||
setConfig({ ...config, authStrategy: 'NONE' });
|
||||
setConfig({ ...config, authStrategy: AuthStrategy.NONE });
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@ -172,6 +172,8 @@ function downloadSongOnFinishSetup({
|
||||
let duration: number | undefined;
|
||||
let time = 0;
|
||||
|
||||
const defaultDownloadFolder = app.getPath('downloads');
|
||||
|
||||
registerCallback((songInfo: SongInfo) => {
|
||||
if (
|
||||
!songInfo.isPaused &&
|
||||
@ -185,7 +187,9 @@ function downloadSongOnFinishSetup({
|
||||
) {
|
||||
downloadSong(
|
||||
currentUrl,
|
||||
config.downloadOnFinish.folder ?? config.downloadFolder,
|
||||
config.downloadOnFinish.folder ??
|
||||
config.downloadFolder ??
|
||||
defaultDownloadFolder,
|
||||
);
|
||||
} else if (
|
||||
config.downloadOnFinish.mode === 'percent' &&
|
||||
@ -193,7 +197,9 @@ function downloadSongOnFinishSetup({
|
||||
) {
|
||||
downloadSong(
|
||||
currentUrl,
|
||||
config.downloadOnFinish.folder ?? config.downloadFolder,
|
||||
config.downloadOnFinish.folder ??
|
||||
config.downloadFolder ??
|
||||
defaultDownloadFolder,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +51,13 @@ interface YouTubeMusicAppElement extends HTMLElement {
|
||||
}
|
||||
|
||||
async function onApiLoaded() {
|
||||
// Workaround for #2459
|
||||
document
|
||||
.querySelector('button.video-button.ytmusic-av-toggle')
|
||||
?.addEventListener('click', () =>
|
||||
window.dispatchEvent(new Event('resize')),
|
||||
);
|
||||
|
||||
window.ipcRenderer.on('ytmd:previous-video', () => {
|
||||
document
|
||||
.querySelector<HTMLElement>('.previous-button.ytmusic-player-bar')
|
||||
@ -129,8 +136,8 @@ async function onApiLoaded() {
|
||||
}
|
||||
};
|
||||
|
||||
window.ipcRenderer.on('ytmd:get-fullscreen', (event) => {
|
||||
event.sender.send('ytmd:set-fullscreen', isFullscreen());
|
||||
window.ipcRenderer.on('ytmd:get-fullscreen', () => {
|
||||
window.ipcRenderer.send('ytmd:set-fullscreen', isFullscreen());
|
||||
});
|
||||
|
||||
window.ipcRenderer.on(
|
||||
@ -148,9 +155,9 @@ async function onApiLoaded() {
|
||||
?.onVolumeTap();
|
||||
});
|
||||
|
||||
window.ipcRenderer.on('ytmd:get-queue', (event) => {
|
||||
window.ipcRenderer.on('ytmd:get-queue', () => {
|
||||
const queue = document.querySelector<QueueElement>('#queue');
|
||||
event.sender.send('ytmd:get-queue-response', {
|
||||
window.ipcRenderer.send('ytmd:get-queue-response', {
|
||||
items: queue?.queue.getItems(),
|
||||
autoPlaying: queue?.queue.autoPlaying,
|
||||
continuation: queue?.queue.continuation,
|
||||
@ -237,16 +244,16 @@ async function onApiLoaded() {
|
||||
'options.likeButtons',
|
||||
);
|
||||
if (likeButtonsOptions) {
|
||||
const likeButtons: HTMLElement | null = document.querySelector(
|
||||
'ytmusic-like-button-renderer',
|
||||
);
|
||||
if (likeButtons) {
|
||||
likeButtons.style.display =
|
||||
{
|
||||
hide: 'none',
|
||||
force: 'inherit',
|
||||
}[likeButtonsOptions] || '';
|
||||
}
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
ytmusic-player-bar[is-mweb-player-bar-modernization-enabled] .middle-controls-buttons.ytmusic-player-bar, #like-button-renderer {
|
||||
display: ${likeButtonsOptions === 'hide' ? 'none' : 'inherit'} !important;
|
||||
}
|
||||
ytmusic-player-bar[is-mweb-player-bar-modernization-enabled] .middle-controls.ytmusic-player-bar {
|
||||
justify-content: ${likeButtonsOptions === 'hide' ? 'flex-start' : 'space-between'} !important;
|
||||
}`;
|
||||
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -73,6 +73,8 @@ tp-yt-paper-item.ytmusic-guide-entry-renderer::before {
|
||||
#av-id ~ #player.ytmusic-player-page:not([player-ui-state="FULLSCREEN"]) {
|
||||
margin-top: auto !important;
|
||||
margin-bottom: auto !important;
|
||||
margin-left: var(--ytmusic-player-page-vertical-padding);
|
||||
margin-right: var(--ytmusic-player-page-vertical-padding);
|
||||
max-height: calc(100% - (var(--ytmusic-player-page-vertical-padding) * 2));
|
||||
max-width: calc(100% - var(--ytmusic-player-page-vertical-padding) * 2);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user