Compare commits

...

157 Commits

Author SHA1 Message Date
TC
b843825f72 Bump version to 1.18.0 2022-09-05 08:48:06 +02:00
b66d3bc3d4 Merge pull request #816 from th-ch/fix-downloader
Bump ytdl-core (bug fix)
2022-09-05 08:45:22 +02:00
TC
9adabd41d9 Bump ytdl-core (bug fix) 2022-09-05 00:43:58 +02:00
TC
3f3df09819 Set NODE_ENV programmatically 2022-09-05 00:38:24 +02:00
TC
1f5f597561 Prepare migration for sandboxing (path.join in preload) 2022-09-05 00:31:29 +02:00
TC
91e4433aba Hide login button in plugin 2022-09-04 23:56:41 +02:00
2d3ce4a8b3 Merge pull request #813 from th-ch/fix-tests
Bump electron and fix tests in CI
2022-09-04 23:36:19 +02:00
TC
971b7f05c5 Bump electron to latest version 2022-09-04 22:32:16 +02:00
TC
bb6115fec1 Remove jest 2022-09-04 22:24:09 +02:00
TC
2a6dc30366 Remove test environment 2022-09-04 22:24:09 +02:00
TC
5e2d843742 Migrate to playwright/test 2022-09-04 22:24:09 +02:00
TC
7aaef26cc8 Add electron flags in tests 2022-09-04 22:21:51 +02:00
TC
0a08eaaa3c Skip downloading browsers in playwright 2022-09-04 22:21:43 +02:00
TC
0d22446f20 Bump playwright and electron 2022-08-25 23:03:38 +02:00
a0543d15a6 Merge pull request #800 from th-ch/custom-css-file
Allow user to pass custom CSS file
2022-08-25 22:52:42 +02:00
TC
e62ee35b42 Rename cssFiles option to themes and add menu entry 2022-08-25 22:50:33 +02:00
TC
ef6fb402bf Allow user to pass custom CSS file 2022-08-21 23:36:02 +02:00
a8301f44be Merge pull request #799 from th-ch/snyk-upgrade-260cfeaaaf6aa5359f78e9f589e807a0
[Snyk] Upgrade html-to-text from 8.2.0 to 8.2.1
2022-08-21 22:54:52 +02:00
1ead86a220 Merge pull request #772 from th-ch/snyk-upgrade-3ee19fd614169422c21264d4fe016fa1
[Snyk] Upgrade electron-store from 8.0.1 to 8.0.2
2022-08-21 22:54:14 +02:00
03e716fe17 Merge pull request #756 from th-ch/dependabot/npm_and_yarn/jpeg-js-0.4.4
Bump jpeg-js from 0.4.3 to 0.4.4
2022-08-21 22:53:54 +02:00
f0bb328981 Merge pull request #749 from foonathan/master
Support MPRIS loop and volume change
2022-08-21 22:53:29 +02:00
f40183f0ca fix: upgrade html-to-text from 8.2.0 to 8.2.1
Snyk has created this PR to upgrade html-to-text from 8.2.0 to 8.2.1.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-08-20 22:45:27 +00:00
5b004acdc1 Update readme.md 2022-08-12 20:19:49 +02:00
0f96da9928 Change volume observer 2022-07-14 20:59:35 +02:00
dfba3d9c2d Support precise volume changes in MPRIS when possible 2022-07-11 20:20:13 +02:00
d9c51063f4 Add MPRIS volume control
Fixes #776.
2022-07-11 19:55:16 +02:00
cd9012691a fix: upgrade electron-store from 8.0.1 to 8.0.2
Snyk has created this PR to upgrade electron-store from 8.0.1 to 8.0.2.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-07-01 19:06:53 +00:00
2499f574ef Use triple equals in more places 2022-06-26 19:43:03 +02:00
e7e873866d Use triple equals 2022-06-26 17:37:25 +02:00
4ccbc741b8 Merge pull request #742 from th-ch/snyk-upgrade-45c0c305b24fe7d1bf9286a6c90b0320
[Snyk] Upgrade @cliqz/adblocker-electron from 1.23.7 to 1.23.8
2022-06-25 21:23:52 +02:00
8ec965a1a4 Merge pull request #745 from lukaszg84/master
Use ; instead of space for play/pause.
2022-06-25 21:22:50 +02:00
0936e9a258 Merge pull request #750 from EsmailELBoBDev2/patch-1
Update readme.md
2022-06-25 21:20:10 +02:00
32a5597573 Merge pull request #753 from Araxeus/fix-lyrics-font-size
fix lyrics font size
2022-06-25 21:18:44 +02:00
9932fd7647 Fix mpris playback status on play/pause 2022-06-22 18:30:49 +02:00
68429be1ce Bump jpeg-js from 0.4.3 to 0.4.4
Bumps [jpeg-js](https://github.com/eugeneware/jpeg-js) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/eugeneware/jpeg-js/releases)
- [Commits](https://github.com/eugeneware/jpeg-js/compare/v0.4.3...v0.4.4)

---
updated-dependencies:
- dependency-name: jpeg-js
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-17 01:59:01 +00:00
d7ac493337 fix lyrics font size 2022-06-16 17:21:13 +03:00
686a0a340e Update readme.md 2022-06-14 20:37:32 +02:00
bba499044b Style changes for review 2022-06-14 19:43:36 +02:00
6e1c50ede1 Support MPRIS loop 2022-06-14 15:45:58 +02:00
6e739e2723 Use ; instead of space for play/pause. 2022-06-10 11:08:36 +02:00
86029a0a73 fix: upgrade @cliqz/adblocker-electron from 1.23.7 to 1.23.8
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.23.7 to 1.23.8.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-06-07 19:11:28 +00:00
b458925aa6 Merge pull request #734 from Araxeus/fix-in-app-menu-top-gap
fix top gap between nav-bar and browse-page
2022-06-06 20:51:11 +02:00
86a1c3c850 Merge pull request #605 from Araxeus/migrate-from-remote-to-ipc
migrate from remote to ipc + fix restart in portable app
2022-06-06 20:48:59 +02:00
8666f934cd Merge pull request #717 from th-ch/snyk-upgrade-7b576b920d24e12003dead59064f8564
[Snyk] Upgrade custom-electron-prompt from 1.4.2 to 1.5.0
2022-06-06 20:40:40 +02:00
59a93916a8 Merge pull request #685 from Araxeus/pip-part-2
Picture in Picture v2
2022-06-06 20:36:18 +02:00
f06a3c8c70 fix top gap between nav-bar and browse-page 2022-05-28 19:22:14 +03:00
7fe937b21e lint 2022-05-20 19:26:55 +03:00
a4aa22aae9 update Electron 2022-05-18 20:21:37 +03:00
TC
96b2aab683 Bump version and changelog to 1.17.0 2022-05-16 18:47:00 +02:00
54d25a26c7 fix: upgrade custom-electron-prompt from 1.4.2 to 1.5.0
Snyk has created this PR to upgrade custom-electron-prompt from 1.4.2 to 1.5.0.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-05-12 19:29:13 +00:00
f5622970c6 Merge branch 'master' into migrate-from-remote-to-ipc 2022-05-01 23:09:13 +03:00
ea09825ece Merge branch 'master' into pip-part-2 2022-05-01 23:06:30 +03:00
247764b64b Merge pull request #712 from th-ch/dependabot/npm_and_yarn/ejs-3.1.7
Bump ejs from 3.1.6 to 3.1.7
2022-05-01 22:04:08 +02:00
5e187b47d8 Merge pull request #693 from Araxeus/fix-event-listener-overload
fix injectCSS `did-finish-load` listener overload
2022-05-01 22:03:01 +02:00
1194befa48 Merge pull request #689 from th-ch/snyk-upgrade-809de6dcc81fbd20573d9ed4667b0c70
[Snyk] Upgrade @cliqz/adblocker-electron from 1.23.6 to 1.23.7
2022-05-01 22:01:17 +02:00
74d3358487 Merge branch 'master' into snyk-upgrade-809de6dcc81fbd20573d9ed4667b0c70 2022-05-01 21:59:41 +02:00
769a613ea5 Merge pull request #686 from th-ch/snyk-upgrade-ded3705d02b70c981cf38d50be5ccece
[Snyk] Upgrade custom-electron-prompt from 1.4.1 to 1.4.2
2022-05-01 21:59:00 +02:00
7280e02709 Bump ejs from 3.1.6 to 3.1.7
Bumps [ejs](https://github.com/mde/ejs) from 3.1.6 to 3.1.7.
- [Release notes](https://github.com/mde/ejs/releases)
- [Changelog](https://github.com/mde/ejs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mde/ejs/compare/v3.1.6...v3.1.7)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-01 19:54:27 +00:00
7b3a767003 Merge pull request #684 from th-ch/snyk-upgrade-82480fa720c2520dafdd84fa5f54d19b
[Snyk] Upgrade @electron/remote from 2.0.7 to 2.0.8
2022-05-01 21:54:09 +02:00
96b0d4e367 Merge pull request #699 from Araxeus/improve-plugin-submenu-ux
Improve plugin submenu ux
2022-05-01 21:53:38 +02:00
TC
ae8365f721 Bump electron-builder to fix Mac build script 2022-05-01 21:52:42 +02:00
8d85bbf5ec Merge pull request #702 from Araxeus/update-build-action
update build action
2022-05-01 21:51:11 +02:00
61cd2ef9dc Merge pull request #700 from Araxeus/rip-video-toggle-plugin
add different modes to video-toggle plugin
2022-05-01 21:43:06 +02:00
3394d647a1 Merge pull request #701 from Araxeus/lint
lint
2022-05-01 21:35:36 +02:00
882ad63fa8 Merge pull request #703 from Araxeus/imgbot
[ImgBot] Optimize images
2022-05-01 21:34:31 +02:00
5fd88ce522 Merge pull request #695 from Araxeus/add-album-to-lastfm
add album to lastfm if available
2022-05-01 21:33:16 +02:00
de280195c5 Merge pull request #680 from Araxeus/in-app-menu-hide-icon-option
[in-app-menu] add hide icon option
2022-05-01 21:32:10 +02:00
7bd69e447a update Electron 2022-04-30 15:24:37 +03:00
357f12c4d1 [ImgBot] Optimize images
*Total -- 828.18kb -> 733.19kb (11.47%)

/assets/youtube-music-tray.png -- 3.29kb -> 1.44kb (56.14%)
/web/youtube-music.svg -- 9.23kb -> 5.75kb (37.67%)
/web/screenshot.jpg -- 815.67kb -> 726.00kb (10.99%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2022-04-23 02:52:10 +03:00
d164cd6fb9 update build action 2022-04-22 23:48:32 +03:00
5d3dc6442f stylelint: declaration-block-no-duplicate-properties 2022-04-22 16:58:49 +03:00
cb7c9bda16 eslint: no-unused-vars 2022-04-22 16:58:41 +03:00
6f2552814f stylelint: declaration-block-no-shorthand-property-overrides 2022-04-22 16:58:41 +03:00
9beebd3772 add different modes to video-toggle plugin
* Custom: like before but slightly position

* Native: use the native video-toggle

* Disabled: force disable the native video-toggle
2022-04-20 18:46:59 +03:00
7cd9506122 add separator after plugin enabled button 2022-04-20 18:27:34 +03:00
f6de5c7c22 PiP options defaults + migrations 2022-04-20 15:21:10 +03:00
2ac3df0455 add album to lastfm if available 2022-04-18 18:45:53 +03:00
2dfe098521 fix injectCSS did-finish-load listener overload 2022-04-17 18:20:08 +03:00
77d4e9cb84 add optional PiP hotkey 2022-04-15 15:57:48 +03:00
b420998458 disable the video-toggle button when in PiP mode 2022-04-15 15:27:31 +03:00
feb06b015e dont save maximized state in PiP mode 2022-04-15 15:27:16 +03:00
09ba760aff fix: upgrade @cliqz/adblocker-electron from 1.23.6 to 1.23.7
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.23.6 to 1.23.7.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-04-13 17:15:33 +00:00
0f192aab2b fix precise-volume position in PiP 2022-04-13 17:17:25 +03:00
c992ec4607 fix: upgrade custom-electron-prompt from 1.4.1 to 1.4.2
Snyk has created this PR to upgrade custom-electron-prompt from 1.4.1 to 1.4.2.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-04-11 17:03:23 +00:00
30840804fa fix: sync pip and index.js options 2022-04-11 00:15:19 +03:00
8000a8326f fix: in-app-menu bg color edge case
when item weren't initially rendered because the window was too small, but were then accessed via the arrow keys - backgrounds was missing
2022-04-10 21:39:06 +03:00
d23bfe9368 v3 2022-04-10 21:16:43 +03:00
047085e72b fix: upgrade @electron/remote from 2.0.7 to 2.0.8
Snyk has created this PR to upgrade @electron/remote from 2.0.7 to 2.0.8.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-04-10 16:51:23 +00:00
768ec7bda7 v2 2022-04-10 19:19:20 +03:00
c25a6f9d2a launch pip from video overlay v1 2022-04-10 01:12:06 +03:00
23058729f3 Merge pull request #682 from th-ch/bypass-age-restrictions-plugin
Add plugin to bypass age restrictions
2022-04-09 23:21:46 +02:00
TC
a1c6dfb199 Add plugin for age restrictions 2022-04-09 23:00:02 +02:00
TC
89ebc230e0 Add plugin to bypass age restrictions (with userscript) 2022-04-09 22:57:57 +02:00
b4b785d773 feat: in-app-menu hideIcon option 2022-04-09 22:56:01 +03:00
57ec0a463d Merge pull request #674 from th-ch/picture-in-picture-plugin
Add "Picture in picture" plugin
2022-04-09 21:53:34 +02:00
TC
6be9b76550 Improve style and remove draggable CSS that blocks login 2022-04-09 16:59:44 +02:00
TC
ebe3baf4bc Make PiP window bigger (follow up: make it configurable) 2022-04-09 16:59:24 +02:00
648d540ca9 Merge pull request #679 from th-ch/thibaut.c/lyrics-metadata-genius
Set lyrics metadata from Genius
2022-04-09 16:54:49 +02:00
cb910a6fd7 Merge branch 'master' into migrate-from-remote-to-ipc 2022-04-09 17:04:06 +03:00
TC
e071f768b4 Force minimist 1.2.6 to fix vuln 2022-04-09 15:21:59 +02:00
TC
05b6435a5c nit: lint package.json 2022-04-09 15:03:06 +02:00
TC
71e9f280a1 Set lyrics metadata if Genius plugin is enabled 2022-04-09 15:02:54 +02:00
TC
dbc34e6d0d Export fetchFromGenius util 2022-04-09 15:02:11 +02:00
TC
d0532d691e Process lyrics HTML in Genius util 2022-04-09 15:02:06 +02:00
2f218ef108 Merge pull request #677 from th-ch/tray-app-hidden
MacOS: bring back the app in dock when using tray + app hidden
2022-04-09 12:17:06 +02:00
TC
14326d2440 Only save size/position if not in PiP 2022-04-09 12:16:30 +02:00
TC
d37e557f79 Block some shortcuts (esc, f) in PiP 2022-04-09 12:15:38 +02:00
TC
5ca0c6b8a9 Ensure player is open when going PiP + add class 2022-04-09 12:14:42 +02:00
TC
e58a580b2b Restore fullscreenable after switching to PiP mode 2022-04-09 12:13:33 +02:00
TC
f3641f5072 Set in config when PiP mode is enabled 2022-04-09 12:11:05 +02:00
TC
296ecb6740 Always inject style 2022-04-09 12:10:22 +02:00
28b5645a56 Merge branch 'master' into migrate-from-remote-to-ipc 2022-04-08 17:00:49 +03:00
742a949680 Update plugins/picture-in-picture/back.js
Co-authored-by: Araxeus <oaeben@gmail.com>
2022-04-08 15:45:33 +02:00
57290c4164 Update plugins/picture-in-picture/back.js
Co-authored-by: Araxeus <oaeben@gmail.com>
2022-04-08 15:44:03 +02:00
6d5fe9561e Update plugins/picture-in-picture/back.js
Co-authored-by: Araxeus <oaeben@gmail.com>
2022-04-08 15:43:57 +02:00
735901095f Merge pull request #644 from th-ch/snyk-upgrade-1b73c27fe4a98c0629769eba2ee53e6a
[Snyk] Upgrade @electron/remote from 2.0.4 to 2.0.5
2022-04-08 15:42:35 +02:00
27454ab527 Merge pull request #660 from th-ch/snyk-upgrade-25706373301b148d087cae43d9039d28
[Snyk] Upgrade ytpl from 2.2.3 to 2.3.0
2022-04-08 15:41:46 +02:00
TC
c345d2cb34 Merge branch 'master' of github.com:th-ch/youtube-music into snyk-upgrade
* 'master' of github.com:th-ch/youtube-music:
  Bump plist from 3.0.2 to 3.0.5
  use spread syntax instead of Array.from
  Fix lyrics genius missing parts
  feat: option to force show like buttons
  Fix lyrics genius missing parts
  [precise-volume] fix expand-volume-slider not updating its value
  fix: upgrade ytdl-core from 4.10.1 to 4.11.0
  add always-on-top option
  fix volumeHud position in miniplayer
  fix: upgrade @cliqz/adblocker-electron from 1.23.4 to 1.23.5
2022-04-08 15:41:14 +02:00
1da297a356 Merge pull request #659 from th-ch/snyk-upgrade-943da220bfc070d47a809365f06e4136
[Snyk] Upgrade ytdl-core from 4.10.1 to 4.11.0
2022-04-08 15:33:12 +02:00
8ebdaf6fa0 Merge branch 'master' into snyk-upgrade-1b73c27fe4a98c0629769eba2ee53e6a 2022-04-08 15:31:29 +02:00
d4d82867f5 Merge pull request #678 from th-ch/dependabot/npm_and_yarn/plist-3.0.5
Bump plist from 3.0.2 to 3.0.5
2022-04-08 15:30:44 +02:00
2e99d6b9bb Bump plist from 3.0.2 to 3.0.5
Bumps [plist](https://github.com/TooTallNate/node-plist) from 3.0.2 to 3.0.5.
- [Release notes](https://github.com/TooTallNate/node-plist/releases)
- [Changelog](https://github.com/TooTallNate/plist.js/blob/master/History.md)
- [Commits](https://github.com/TooTallNate/node-plist/commits)

---
updated-dependencies:
- dependency-name: plist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-08 13:25:18 +00:00
a1e740b881 Merge pull request #624 from th-ch/snyk-upgrade-8a29fb2f6f9f73947c0629bb91d5a7f1
[Snyk] Upgrade @cliqz/adblocker-electron from 1.23.4 to 1.23.5
2022-04-08 15:24:47 +02:00
TC
de14d64927 nit: re-align quit menu 2022-04-08 15:17:36 +02:00
TC
6c93d635d0 Bring back the app in the dock (MacOS) when it was hidden 2022-04-08 15:17:26 +02:00
f4df6fceee Merge branch 'migrate-from-remote-to-ipc' of https://github.com/Araxeus/youtube-music into migrate-from-remote-to-ipc 2022-04-07 22:12:54 +03:00
d69c8a754e Merge branch 'local-upstream/master' into migrate-from-remote-to-ipc 2022-04-07 22:12:25 +03:00
c3d90d8b27 update Electron
+ update electron-unhandled
2022-04-07 22:09:54 +03:00
10681e4e99 Merge pull request #645 from Araxeus/fix-volumeHud-position-on-miniplayer
[Precise-Volume] fix volumeHud position in miniplayer
2022-04-07 21:02:22 +02:00
48aa3ba0d8 Merge pull request #655 from Araxeus/add-always-on-top-option
add always-on-top option
2022-04-07 21:01:15 +02:00
f98e4ea749 Merge pull request #670 from Araxeus/fix-precise-volume-not-updating-expand-slider
[precise-volume] fix expand-volume-slider not updating its value
2022-04-07 21:00:19 +02:00
dc500efb79 Merge pull request #671 from Araxeus/lyrics]-fix-part-of-lyrics-is-missing
Fix lyrics genius missing parts
2022-04-07 20:59:30 +02:00
8d9dafb149 Merge pull request #673 from Araxeus/force-show-like-buttons
feat: option to force show like buttons
2022-04-07 20:56:34 +02:00
TC
4ddd2f339b add PiP plugin to readme list 2022-04-07 20:05:13 +02:00
TC
d2265b59d7 Create first version of picture in picture plugin 2022-04-07 20:01:29 +02:00
d47b03c23d use spread syntax instead of Array.from 2022-04-06 20:15:24 +03:00
4c857cb9e9 Merge branch 'lyrics]-fix-part-of-lyrics-is-missing' of https://github.com/Araxeus/youtube-music into lyrics]-fix-part-of-lyrics-is-missing 2022-04-06 20:05:22 +03:00
c31f6cc797 Fix lyrics genius missing parts 2022-04-06 20:04:26 +03:00
0d3fa261a7 feat: option to force show like buttons 2022-04-06 19:48:58 +03:00
b6ee861166 Fix lyrics genius missing parts 2022-04-06 18:57:39 +03:00
f9cf12b7d3 [precise-volume] fix expand-volume-slider not updating its value 2022-04-06 18:26:05 +03:00
bed8d0a7f2 update Electron
+ update electron-unhandled
2022-03-30 18:47:51 +03:00
afac520ff8 fix: upgrade ytpl from 2.2.3 to 2.3.0
Snyk has created this PR to upgrade ytpl from 2.2.3 to 2.3.0.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-03-26 17:12:51 +00:00
1332c66050 fix: upgrade ytdl-core from 4.10.1 to 4.11.0
Snyk has created this PR to upgrade ytdl-core from 4.10.1 to 4.11.0.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-03-26 17:12:48 +00:00
7f08579671 add always-on-top option 2022-03-21 18:17:22 +02:00
d5e4f3af46 fix volumeHud position in miniplayer 2022-03-14 22:00:57 +02:00
bdceb4d462 fix: upgrade @electron/remote from 2.0.4 to 2.0.5
Snyk has created this PR to upgrade @electron/remote from 2.0.4 to 2.0.5.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-03-14 18:16:47 +00:00
2758a44965 fix: upgrade @cliqz/adblocker-electron from 1.23.4 to 1.23.5
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.23.4 to 1.23.5.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/th-ch/project/81809c53-bb7b-46b9-a0d7-806d45d74ac6?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-02-28 18:26:03 +00:00
704fba9aba fix portable app restart 2022-02-24 20:52:16 +02:00
5cffb6f062 Merge pull request #619 from Araxeus/fix-prompt-options
fix custom titlebar in prompt options
2022-02-24 19:27:56 +01:00
407887254f Merge branch 'master' into migrate-from-remote-to-ipc 2022-02-23 17:34:24 +02:00
47729130c9 fix custom titlebar in prompt options 2022-02-23 17:15:10 +02:00
7088941179 update electron 2022-02-22 18:27:45 +02:00
TC
ff39ddb277 Update changelog 2022-02-20 19:15:07 +01:00
1eeaf1dd0a remove @electron/remote 2022-02-20 19:38:42 +02:00
9abf7a77d8 Merge branch 'local-upstream/master' into migrate-from-remote-to-ipc 2022-02-20 19:34:18 +02:00
5bd97685b9 migrate from remote to ipc 2022-02-13 23:45:53 +02:00
52 changed files with 1470 additions and 2372 deletions

View File

@ -13,18 +13,18 @@ jobs:
os: [macos-latest, ubuntu-latest, windows-latest] os: [macos-latest, ubuntu-latest, windows-latest]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup NodeJS - name: Setup NodeJS
uses: actions/setup-node@v1 uses: actions/setup-node@v3
with: with:
node-version: "14.x" node-version: "16.x"
- name: Get yarn cache directory path - name: Get yarn cache directory path
id: yarn-cache-dir-path id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)" run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2 - uses: actions/cache@v3
id: yarn-cache id: yarn-cache
with: with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
@ -91,6 +91,8 @@ jobs:
- name: Test - name: Test
uses: GabrielBB/xvfb-action@v1 uses: GabrielBB/xvfb-action@v1
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
with: with:
run: yarn test run: yarn test

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -2,8 +2,67 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [v1.18.0](https://github.com/th-ch/youtube-music/compare/v1.17.0...v1.18.0)
- Bump ytdl-core (bug fix) [`#816`](https://github.com/th-ch/youtube-music/pull/816)
- Bump electron and fix tests in CI [`#813`](https://github.com/th-ch/youtube-music/pull/813)
- Allow user to pass custom CSS file [`#800`](https://github.com/th-ch/youtube-music/pull/800)
- [Snyk] Upgrade html-to-text from 8.2.0 to 8.2.1 [`#799`](https://github.com/th-ch/youtube-music/pull/799)
- [Snyk] Upgrade electron-store from 8.0.1 to 8.0.2 [`#772`](https://github.com/th-ch/youtube-music/pull/772)
- Bump jpeg-js from 0.4.3 to 0.4.4 [`#756`](https://github.com/th-ch/youtube-music/pull/756)
- Support MPRIS loop and volume change [`#749`](https://github.com/th-ch/youtube-music/pull/749)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.23.7 to 1.23.8 [`#742`](https://github.com/th-ch/youtube-music/pull/742)
- Use ; instead of space for play/pause. [`#745`](https://github.com/th-ch/youtube-music/pull/745)
- Update readme.md [`#750`](https://github.com/th-ch/youtube-music/pull/750)
- fix lyrics font size [`#753`](https://github.com/th-ch/youtube-music/pull/753)
- fix top gap between nav-bar and browse-page [`#734`](https://github.com/th-ch/youtube-music/pull/734)
- migrate from remote to ipc + fix restart in portable app [`#605`](https://github.com/th-ch/youtube-music/pull/605)
- [Snyk] Upgrade custom-electron-prompt from 1.4.2 to 1.5.0 [`#717`](https://github.com/th-ch/youtube-music/pull/717)
- Picture in Picture v2 [`#685`](https://github.com/th-ch/youtube-music/pull/685)
- Add MPRIS volume control [`#776`](https://github.com/th-ch/youtube-music/issues/776)
- Remove jest [`bb6115f`](https://github.com/th-ch/youtube-music/commit/bb6115fec1a18a416edb365a442eb0b0ee330768)
- migrate from remote to ipc [`5bd9768`](https://github.com/th-ch/youtube-music/commit/5bd97685b9e07c656e0b57a9e02819afc70af1b1)
- v3 [`d23bfe9`](https://github.com/th-ch/youtube-music/commit/d23bfe936840b947ca101fd304464f65d36e88cc)
#### [v1.17.0](https://github.com/th-ch/youtube-music/compare/v1.16.0...v1.17.0)
> 16 May 2022
- Bump ejs from 3.1.6 to 3.1.7 [`#712`](https://github.com/th-ch/youtube-music/pull/712)
- fix injectCSS `did-finish-load` listener overload [`#693`](https://github.com/th-ch/youtube-music/pull/693)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.23.6 to 1.23.7 [`#689`](https://github.com/th-ch/youtube-music/pull/689)
- [Snyk] Upgrade custom-electron-prompt from 1.4.1 to 1.4.2 [`#686`](https://github.com/th-ch/youtube-music/pull/686)
- [Snyk] Upgrade @electron/remote from 2.0.7 to 2.0.8 [`#684`](https://github.com/th-ch/youtube-music/pull/684)
- Improve plugin submenu ux [`#699`](https://github.com/th-ch/youtube-music/pull/699)
- update build action [`#702`](https://github.com/th-ch/youtube-music/pull/702)
- add different modes to video-toggle plugin [`#700`](https://github.com/th-ch/youtube-music/pull/700)
- lint [`#701`](https://github.com/th-ch/youtube-music/pull/701)
- [ImgBot] Optimize images [`#703`](https://github.com/th-ch/youtube-music/pull/703)
- add album to lastfm if available [`#695`](https://github.com/th-ch/youtube-music/pull/695)
- [in-app-menu] add hide icon option [`#680`](https://github.com/th-ch/youtube-music/pull/680)
- Add plugin to bypass age restrictions [`#682`](https://github.com/th-ch/youtube-music/pull/682)
- Add "Picture in picture" plugin [`#674`](https://github.com/th-ch/youtube-music/pull/674)
- Set lyrics metadata from Genius [`#679`](https://github.com/th-ch/youtube-music/pull/679)
- MacOS: bring back the app in dock when using tray + app hidden [`#677`](https://github.com/th-ch/youtube-music/pull/677)
- [Snyk] Upgrade @electron/remote from 2.0.4 to 2.0.5 [`#644`](https://github.com/th-ch/youtube-music/pull/644)
- [Snyk] Upgrade ytpl from 2.2.3 to 2.3.0 [`#660`](https://github.com/th-ch/youtube-music/pull/660)
- [Snyk] Upgrade ytdl-core from 4.10.1 to 4.11.0 [`#659`](https://github.com/th-ch/youtube-music/pull/659)
- Bump plist from 3.0.2 to 3.0.5 [`#678`](https://github.com/th-ch/youtube-music/pull/678)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.23.4 to 1.23.5 [`#624`](https://github.com/th-ch/youtube-music/pull/624)
- [Precise-Volume] fix volumeHud position in miniplayer [`#645`](https://github.com/th-ch/youtube-music/pull/645)
- add always-on-top option [`#655`](https://github.com/th-ch/youtube-music/pull/655)
- [precise-volume] fix expand-volume-slider not updating its value [`#670`](https://github.com/th-ch/youtube-music/pull/670)
- Fix lyrics genius missing parts [`#671`](https://github.com/th-ch/youtube-music/pull/671)
- feat: option to force show like buttons [`#673`](https://github.com/th-ch/youtube-music/pull/673)
- fix custom titlebar in prompt options [`#619`](https://github.com/th-ch/youtube-music/pull/619)
- Process lyrics HTML in Genius util [`d0532d6`](https://github.com/th-ch/youtube-music/commit/d0532d691e56f955ef0b41f5fe2efe6295dddf9e)
- Create first version of picture in picture plugin [`d2265b5`](https://github.com/th-ch/youtube-music/commit/d2265b59d78143cf51fe4dc3d5dee9da66873cc1)
- Bump electron-builder to fix Mac build script [`ae8365f`](https://github.com/th-ch/youtube-music/commit/ae8365f721eafda6c502d02eee86d098f2b9e2a1)
#### [v1.16.0](https://github.com/th-ch/youtube-music/compare/v1.15.0...v1.16.0) #### [v1.16.0](https://github.com/th-ch/youtube-music/compare/v1.15.0...v1.16.0)
> 20 February 2022
- update in-app-menu [`#596`](https://github.com/th-ch/youtube-music/pull/596) - update in-app-menu [`#596`](https://github.com/th-ch/youtube-music/pull/596)
- Fix clientID [`#602`](https://github.com/th-ch/youtube-music/pull/602) - Fix clientID [`#602`](https://github.com/th-ch/youtube-music/pull/602)
- Add snoretoast custom compile script [`#600`](https://github.com/th-ch/youtube-music/pull/600) - Add snoretoast custom compile script [`#600`](https://github.com/th-ch/youtube-music/pull/600)
@ -33,7 +92,7 @@ All notable changes to this project will be documented in this file. Dates are d
- xesam:artist should be a list [`#539`](https://github.com/th-ch/youtube-music/pull/539) - xesam:artist should be a list [`#539`](https://github.com/th-ch/youtube-music/pull/539)
- fix notifications showing thumbnail of last song [`#537`](https://github.com/th-ch/youtube-music/pull/537) - fix notifications showing thumbnail of last song [`#537`](https://github.com/th-ch/youtube-music/pull/537)
- Fix https://github.com/th-ch/youtube-music/pull/578#issuecomment-1035517531 [`#578`](https://github.com/th-ch/youtube-music/pull/578) - Fix https://github.com/th-ch/youtube-music/pull/578#issuecomment-1035517531 [`#578`](https://github.com/th-ch/youtube-music/pull/578)
- Add automatic changelog [`06245fe`](https://github.com/th-ch/youtube-music/commit/06245fe120d92b2f3e94ecb2ea14cfb1f6c8bbb9) - Add automatic changelog [`1d9bfe8`](https://github.com/th-ch/youtube-music/commit/1d9bfe8ac8869cde648164979986964baa52c2f9)
- update electron to v17.0.0 [`fef7115`](https://github.com/th-ch/youtube-music/commit/fef711549fa9862f8ea23301edde747c5802e352) - update electron to v17.0.0 [`fef7115`](https://github.com/th-ch/youtube-music/commit/fef711549fa9862f8ea23301edde747c5802e352)
- update dependencies [`8be07bc`](https://github.com/th-ch/youtube-music/commit/8be07bcb7ad8b727d97c36aa0760aed4e2fc481f) - update dependencies [`8be07bc`](https://github.com/th-ch/youtube-music/commit/8be07bcb7ad8b727d97c36aa0760aed4e2fc481f)

View File

@ -80,8 +80,16 @@ const defaultConfig = {
}, },
"video-toggle": { "video-toggle": {
enabled: false, enabled: false,
mode: "custom",
forceHide: false, forceHide: false,
}, },
"picture-in-picture": {
"enabled": false,
"alwaysOnTop": true,
"savePosition": true,
"saveSize": false,
"hotkey": "P"
},
}, },
}; };

View File

@ -2,7 +2,20 @@ const Store = require("electron-store");
const defaults = require("./defaults"); const defaults = require("./defaults");
const setDefaultPluginOptions = (store, plugin) => {
if (!store.get(`plugins.${plugin}`)) {
store.set(`plugins.${plugin}`, defaults.plugins[plugin]);
}
}
const migrations = { const migrations = {
">=1.17.0": (store) => {
setDefaultPluginOptions(store, "picture-in-picture");
if (store.get("plugins.video-toggle.mode") === undefined) {
store.set("plugins.video-toggle.mode", "custom");
}
},
">=1.14.0": (store) => { ">=1.14.0": (store) => {
if ( if (
typeof store.get("plugins.precise-volume.globalShortcuts") !== "object" typeof store.get("plugins.precise-volume.globalShortcuts") !== "object"

View File

@ -2,8 +2,6 @@
const path = require("path"); const path = require("path");
const electron = require("electron"); const electron = require("electron");
const remote = require('@electron/remote/main');
remote.initialize();
const enhanceWebRequest = require("electron-better-web-request").default; const enhanceWebRequest = require("electron-better-web-request").default;
const is = require("electron-is"); const is = require("electron-is");
const unhandled = require("electron-unhandled"); const unhandled = require("electron-unhandled");
@ -15,6 +13,7 @@ const { fileExists, injectCSS } = require("./plugins/utils");
const { isTesting } = require("./utils/testing"); const { isTesting } = require("./utils/testing");
const { setUpTray } = require("./tray"); const { setUpTray } = require("./tray");
const { setupSongInfo } = require("./providers/song-info"); const { setupSongInfo } = require("./providers/song-info");
const { setupAppControls, restart } = require("./providers/app-controls");
// Catch errors and log them // Catch errors and log them
unhandled({ unhandled({
@ -85,6 +84,22 @@ function onClosed() {
function loadPlugins(win) { function loadPlugins(win) {
injectCSS(win.webContents, path.join(__dirname, "youtube-music.css")); injectCSS(win.webContents, path.join(__dirname, "youtube-music.css"));
// Load user CSS
const themes = config.get("options.themes");
if (Array.isArray(themes)) {
themes.forEach((cssFile) => {
fileExists(
cssFile,
() => {
injectCSS(win.webContents, cssFile);
},
() => {
console.warn(`CSS file "${cssFile}" does not exist, ignoring`);
}
);
});
}
win.webContents.once("did-finish-load", () => { win.webContents.once("did-finish-load", () => {
if (is.dev()) { if (is.dev()) {
console.log("did finish load"); console.log("did finish load");
@ -120,14 +135,13 @@ function createMainWindow() {
contextIsolation: false, contextIsolation: false,
preload: path.join(__dirname, "preload.js"), preload: path.join(__dirname, "preload.js"),
nodeIntegrationInSubFrames: true, nodeIntegrationInSubFrames: true,
nativeWindowOpen: true, // window.open return Window object(like in regular browsers), not BrowserWindowProxy
affinity: "main-window", // main window, and addition windows should work in one process affinity: "main-window", // main window, and addition windows should work in one process
...(isTesting() ...(!isTesting()
? { ? {
// Only necessary when testing with Spectron // Sandbox is only enabled in tests for now
contextIsolation: false, // See https://www.electronjs.org/docs/latest/tutorial/sandbox#preload-scripts
nodeIntegration: true, sandbox: false,
} }
: undefined), : undefined),
}, },
frame: !is.macOS() && !useInlineMenu, frame: !is.macOS() && !useInlineMenu,
@ -138,7 +152,6 @@ function createMainWindow() {
: "default", : "default",
autoHideMenuBar: config.get("options.hideMenu"), autoHideMenuBar: config.get("options.hideMenu"),
}); });
remote.enable(win.webContents);
if (windowPosition) { if (windowPosition) {
const { x, y } = windowPosition; const { x, y } = windowPosition;
@ -165,44 +178,67 @@ function createMainWindow() {
win.maximize(); win.maximize();
} }
if(config.get("options.alwaysOnTop")){
win.setAlwaysOnTop(true);
}
const urlToLoad = config.get("options.resumeOnStart") const urlToLoad = config.get("options.resumeOnStart")
? config.get("url") ? config.get("url")
: config.defaultConfig.url; : config.defaultConfig.url;
win.webContents.loadURL(urlToLoad); win.webContents.loadURL(urlToLoad);
win.on("closed", onClosed); win.on("closed", onClosed);
const setPiPOptions = config.plugins.isEnabled("picture-in-picture")
? (key, value) => require("./plugins/picture-in-picture/back").setOptions({ [key]: value })
: () => {};
win.on("move", () => { win.on("move", () => {
if (win.isMaximized()) return; if (win.isMaximized()) return;
let position = win.getPosition(); let position = win.getPosition();
lateSave("window-position", { x: position[0], y: position[1] }); const isPiPEnabled =
config.plugins.isEnabled("picture-in-picture") &&
config.plugins.getOptions("picture-in-picture")["isInPiP"];
if (!isPiPEnabled) {
lateSave("window-position", { x: position[0], y: position[1] });
} else if(config.plugins.getOptions("picture-in-picture")["savePosition"]) {
lateSave("pip-position", position, setPiPOptions);
}
}); });
let winWasMaximized; let winWasMaximized;
win.on("resize", () => { win.on("resize", () => {
const windowSize = win.getSize(); const windowSize = win.getSize();
const isMaximized = win.isMaximized(); const isMaximized = win.isMaximized();
if (winWasMaximized !== isMaximized) {
const isPiPEnabled =
config.plugins.isEnabled("picture-in-picture") &&
config.plugins.getOptions("picture-in-picture")["isInPiP"];
if (!isPiPEnabled && winWasMaximized !== isMaximized) {
winWasMaximized = isMaximized; winWasMaximized = isMaximized;
config.set("window-maximized", isMaximized); config.set("window-maximized", isMaximized);
} }
if (!isMaximized) { if (isMaximized) return;
if (!isPiPEnabled) {
lateSave("window-size", { lateSave("window-size", {
width: windowSize[0], width: windowSize[0],
height: windowSize[1], height: windowSize[1],
}); });
} else if(config.plugins.getOptions("picture-in-picture")["saveSize"]) {
lateSave("pip-size", windowSize, setPiPOptions);
} }
}); });
let savedTimeouts = {}; let savedTimeouts = {};
function lateSave(key, value) { function lateSave(key, value, fn = config.set) {
if (savedTimeouts[key]) clearTimeout(savedTimeouts[key]); if (savedTimeouts[key]) clearTimeout(savedTimeouts[key]);
savedTimeouts[key] = setTimeout(() => { savedTimeouts[key] = setTimeout(() => {
config.set(key, value); fn(key, value);
savedTimeouts[key] = undefined; savedTimeouts[key] = undefined;
}, 1000) }, 600);
} }
win.webContents.on("render-process-gone", (event, webContents, details) => { win.webContents.on("render-process-gone", (event, webContents, details) => {
@ -249,6 +285,7 @@ app.once("browser-window-created", (event, win) => {
setupSongInfo(win); setupSongInfo(win);
loadPlugins(win); loadPlugins(win);
setupAppControls();
win.webContents.on("did-fail-load", ( win.webContents.on("did-fail-load", (
_event, _event,
@ -436,13 +473,8 @@ function showUnresponsiveDialog(win, details) {
cancelId: 0 cancelId: 0
}).then( result => { }).then( result => {
switch (result.response) { switch (result.response) {
case 1: //if relaunch - relaunch+exit case 1: restart(); break;
app.relaunch(); case 2: app.quit(); break;
case 2:
app.quit();
break;
default:
break;
} }
}); });
} }

69
menu.js
View File

@ -3,6 +3,7 @@ const path = require("path");
const { app, Menu, dialog } = require("electron"); const { app, Menu, dialog } = require("electron");
const is = require("electron-is"); const is = require("electron-is");
const { restart } = require("./providers/app-controls");
const { getAllPlugins } = require("./plugins/utils"); const { getAllPlugins } = require("./plugins/utils");
const config = require("./config"); const config = require("./config");
@ -51,6 +52,7 @@ const mainMenuTemplate = (win) => {
label: plugin, label: plugin,
submenu: [ submenu: [
pluginEnabledMenu(plugin, "Enabled", true, refreshMenu), pluginEnabledMenu(plugin, "Enabled", true, refreshMenu),
{ type: "separator" },
...getPluginMenu(win, config.plugins.getOptions(plugin), refreshMenu), ...getPluginMenu(win, config.plugins.getOptions(plugin), refreshMenu),
], ],
}; };
@ -80,12 +82,53 @@ const mainMenuTemplate = (win) => {
}, },
}, },
{ {
label: "Remove upgrade button", label: "Visual Tweaks",
type: "checkbox", submenu: [
checked: config.get("options.removeUpgradeButton"), {
click: (item) => { label: "Remove upgrade button",
config.setMenuOption("options.removeUpgradeButton", item.checked); type: "checkbox",
}, checked: config.get("options.removeUpgradeButton"),
click: (item) => {
config.setMenuOption("options.removeUpgradeButton", item.checked);
},
},
{
label: "Force show like buttons",
type: "checkbox",
checked: config.get("options.ForceShowLikeButtons"),
click: (item) => {
config.set("options.ForceShowLikeButtons", item.checked);
},
},
{
label: "Theme",
submenu: [
{
label: "No theme",
type: "radio",
checked: !config.get("options.themes"), // todo rename "themes"
click: () => {
config.set("options.themes", []);
},
},
{ type: "separator" },
{
label: "Import custom CSS file",
type: "radio",
checked: false,
click: async () => {
const { filePaths } = await dialog.showOpenDialog({
filters: [{ name: "CSS Files", extensions: ["css"] }],
properties: ["openFile", "multiSelections"],
});
if (filePaths) {
config.set("options.themes", filePaths);
}
},
},
],
},
],
}, },
{ {
label: "Single instance lock", label: "Single instance lock",
@ -100,6 +143,15 @@ const mainMenuTemplate = (win) => {
} }
}, },
}, },
{
label: "Always on top",
type: "checkbox",
checked: config.get("options.alwaysOnTop"),
click: (item) => {
config.setMenuOption("options.alwaysOnTop", item.checked);
win.setAlwaysOnTop(item.checked);
},
},
...(is.windows() || is.linux() ...(is.windows() || is.linux()
? [ ? [
{ {
@ -279,10 +331,7 @@ const mainMenuTemplate = (win) => {
}, },
{ {
label: "Restart App", label: "Restart App",
click: () => { click: restart
app.relaunch();
app.quit();
},
}, },
{ role: "quit" }, { role: "quit" },
], ],

View File

@ -1,7 +1,7 @@
{ {
"name": "youtube-music", "name": "youtube-music",
"productName": "YouTube Music", "productName": "YouTube Music",
"version": "1.16.0", "version": "1.18.0",
"description": "YouTube Music Desktop App - including custom plugins", "description": "YouTube Music Desktop App - including custom plugins",
"license": "MIT", "license": "MIT",
"repository": "th-ch/youtube-music", "repository": "th-ch/youtube-music",
@ -67,7 +67,8 @@
} }
}, },
"scripts": { "scripts": {
"test": "jest", "test": "playwright test",
"test:debug": "DEBUG=pw:browser* playwright test",
"start": "electron .", "start": "electron .",
"start:debug": "ELECTRON_ENABLE_LOGGING=1 electron .", "start:debug": "ELECTRON_ENABLE_LOGGING=1 electron .",
"icon": "rimraf assets/generated && electron-icon-maker --input=assets/youtube-music.png --output=assets/generated", "icon": "rimraf assets/generated && electron-icon-maker --input=assets/youtube-music.png --output=assets/generated",
@ -92,52 +93,54 @@
"npm": "Please use yarn and not npm" "npm": "Please use yarn and not npm"
}, },
"dependencies": { "dependencies": {
"@cliqz/adblocker-electron": "^1.23.4", "@cliqz/adblocker-electron": "^1.23.8",
"@electron/remote": "^2.0.4",
"@ffmpeg/core": "^0.10.0", "@ffmpeg/core": "^0.10.0",
"@ffmpeg/ffmpeg": "^0.10.1", "@ffmpeg/ffmpeg": "^0.10.1",
"Simple-YouTube-Age-Restriction-Bypass": "https://gitpkg.now.sh/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/dist?v2.4.6",
"async-mutex": "^0.3.2", "async-mutex": "^0.3.2",
"browser-id3-writer": "^4.4.0", "browser-id3-writer": "^4.4.0",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
"custom-electron-prompt": "^1.4.1", "custom-electron-prompt": "^1.5.0",
"custom-electron-titlebar": "^4.1.0", "custom-electron-titlebar": "^4.1.0",
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",
"electron-better-web-request": "^1.0.1", "electron-better-web-request": "^1.0.1",
"electron-debug": "^3.2.0", "electron-debug": "^3.2.0",
"electron-is": "^3.0.0", "electron-is": "^3.0.0",
"electron-localshortcut": "^3.2.1", "electron-localshortcut": "^3.2.1",
"electron-store": "^8.0.1", "electron-store": "^8.0.2",
"electron-unhandled": "^3.0.2", "electron-unhandled": "^4.0.1",
"electron-updater": "^4.6.3", "electron-updater": "^4.6.3",
"filenamify": "^4.3.0", "filenamify": "^4.3.0",
"hark": "^1.2.3", "hark": "^1.2.3",
"html-to-text": "^8.2.1",
"md5": "^2.3.0", "md5": "^2.3.0",
"mpris-service": "^2.1.2", "mpris-service": "^2.1.2",
"node-fetch": "^2.6.7", "node-fetch": "^2.6.7",
"node-notifier": "^10.0.1", "node-notifier": "^10.0.1",
"ytdl-core": "^4.10.1", "ytdl-core": "^4.11.1",
"ytpl": "^2.2.3" "ytpl": "^2.3.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.25.1",
"auto-changelog": "^2.4.0", "auto-changelog": "^2.4.0",
"electron": "^17.0.0", "electron": "^20.1.1",
"electron-builder": "^22.14.5", "electron-builder": "^23.0.3",
"electron-devtools-installer": "^3.1.1", "electron-devtools-installer": "^3.1.1",
"electron-icon-maker": "0.0.5", "electron-icon-maker": "0.0.5",
"jest": "^27.3.1", "playwright": "^1.25.1",
"playwright": "^1.17.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"xo": "^0.45.0" "xo": "^0.45.0"
}, },
"resolutions": { "resolutions": {
"glob-parent": "5.1.2", "glob-parent": "5.1.2",
"minimist": "1.2.5", "minimist": "1.2.6",
"yargs-parser": "18.1.3" "yargs-parser": "18.1.3"
}, },
"auto-changelog": { "auto-changelog": {
"hideCredit": true, "hideCredit": true,
"package": true, "package": true,
"output": "changelog.md" "unreleased": true,
"output": "changelog.md"
}, },
"xo": { "xo": {
"envs": [ "envs": [

View File

@ -0,0 +1,4 @@
module.exports = () => {
// See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass#userscript
require("simple-youtube-age-restriction-bypass/Simple-YouTube-Age-Restriction-Bypass.user.js");
};

View File

@ -8,7 +8,9 @@ const registerCallback = require("../../providers/song-info");
const { injectCSS, listenAction } = require("../utils"); const { injectCSS, listenAction } = require("../utils");
const { cropMaxWidth } = require("./utils"); const { cropMaxWidth } = require("./utils");
const { ACTIONS, CHANNEL } = require("./actions.js"); const { ACTIONS, CHANNEL } = require("./actions.js");
const { isEnabled } = require("../../config/plugins");
const { getImage } = require("../../providers/song-info"); const { getImage } = require("../../providers/song-info");
const { fetchFromGenius } = require("../lyrics-genius/back");
const sendError = (win, error) => { const sendError = (win, error) => {
win.setProgressBar(-1); // close progress bar win.setProgressBar(-1); // close progress bar
@ -71,6 +73,15 @@ function handle(win) {
description: "" description: ""
}); });
} }
if (isEnabled("lyrics-genius")) {
const lyrics = await fetchFromGenius(songMetadata);
if (lyrics) {
writer.setFrame("USLT", {
description: lyrics,
lyrics: lyrics,
});
}
}
writer.addTag(); writer.addTag();
fileBuffer = Buffer.from(writer.arrayBuffer); fileBuffer = Buffer.from(writer.arrayBuffer);
} catch (error) { } catch (error) {

View File

@ -3,7 +3,6 @@ const { join } = require("path");
const Mutex = require("async-mutex").Mutex; const Mutex = require("async-mutex").Mutex;
const { ipcRenderer } = require("electron"); const { ipcRenderer } = require("electron");
const remote = require('@electron/remote');
const is = require("electron-is"); const is = require("electron-is");
const filenamify = require("filenamify"); const filenamify = require("filenamify");
@ -137,7 +136,7 @@ const toMP3 = async (
safeVideoName + "." + extension safeVideoName + "." + extension
); );
const folder = options.downloadFolder || remote.app.getPath("downloads"); const folder = options.downloadFolder || await ipcRenderer.invoke('getDownloadsFolder');
const name = metadata.title const name = metadata.title
? `${metadata.artist ? `${metadata.artist} - ` : ""}${metadata.title}` ? `${metadata.artist ? `${metadata.artist} - ` : ""}${metadata.title}`
: videoName; : videoName;

View File

@ -1,9 +1,10 @@
const { ipcRenderer } = require("electron"); const { ipcRenderer } = require("electron");
const config = require("../../config"); const config = require("../../config");
const { Titlebar, Color } = require("custom-electron-titlebar"); const { Titlebar, Color } = require("custom-electron-titlebar");
const { isEnabled } = require("../../config/plugins");
function $(selector) { return document.querySelector(selector); } function $(selector) { return document.querySelector(selector); }
module.exports = () => { module.exports = (options) => {
let visible = !config.get("options.hideMenu"); let visible = !config.get("options.hideMenu");
const bar = new Titlebar({ const bar = new Titlebar({
backgroundColor: Color.fromHex("#050505"), backgroundColor: Color.fromHex("#050505"),
@ -14,6 +15,10 @@ module.exports = () => {
bar.updateTitle(" "); bar.updateTitle(" ");
document.title = "Youtube Music"; document.title = "Youtube Music";
const hideIcon = hide => $('.cet-window-icon').style.display = hide ? 'none' : 'flex';
if (options.hideIcon) hideIcon(true);
ipcRenderer.on("refreshMenu", (_, showMenu) => { ipcRenderer.on("refreshMenu", (_, showMenu) => {
if (showMenu === undefined && !visible) return; if (showMenu === undefined && !visible) return;
if (showMenu === false) { if (showMenu === false) {
@ -25,6 +30,14 @@ module.exports = () => {
} }
}); });
if (isEnabled("picture-in-picture")) {
ipcRenderer.on("pip-toggle", (_, pipEnabled) => {
bar.refreshMenu();
});
}
ipcRenderer.on("hideIcon", (_, hide) => hideIcon(hide));
// Increases the right margin of Navbar background when the scrollbar is visible to avoid blocking it (z-index doesn't affect it) // Increases the right margin of Navbar background when the scrollbar is visible to avoid blocking it (z-index doesn't affect it)
document.addEventListener('apiLoaded', () => { document.addEventListener('apiLoaded', () => {
setNavbarMargin(); setNavbarMargin();

View File

@ -0,0 +1,14 @@
const { setOptions } = require("../../config/plugins");
module.exports = (win, options) => [
{
label: "Hide Icon",
type: "checkbox",
checked: options.hideIcon,
click: (item) => {
win.webContents.send("hideIcon", item.checked);
options.hideIcon = item.checked;
setOptions("in-app-menu", options);
},
}
];

View File

@ -13,6 +13,11 @@
height: 75px !important; height: 75px !important;
} }
/* fixes top gap between nav-bar and browse-page */
#browse-page {
padding-top: 0 !important;
}
/* remove window dragging for nav bar (conflict with titlebar drag) */ /* remove window dragging for nav bar (conflict with titlebar drag) */
ytmusic-nav-bar, ytmusic-nav-bar,
.tab-titleiron-icon, .tab-titleiron-icon,
@ -57,10 +62,10 @@ yt-page-navigation-progress,
/* The scrollbar 'thumb' ...that marque oval shape in a scrollbar */ /* The scrollbar 'thumb' ...that marque oval shape in a scrollbar */
::-webkit-scrollbar-thumb:vertical { ::-webkit-scrollbar-thumb:vertical {
background-clip: padding-box;
border: 2px solid rgba(0, 0, 0, 0); border: 2px solid rgba(0, 0, 0, 0);
background: #3a3a3a; background: #3a3a3a;
background-clip: padding-box;
border-radius: 100px; border-radius: 100px;
-moz-border-radius: 100px; -moz-border-radius: 100px;
-webkit-border-radius: 100px; -webkit-border-radius: 100px;
@ -71,3 +76,7 @@ yt-page-navigation-progress,
-moz-border-radius: 100px; -moz-border-radius: 100px;
-webkit-border-radius: 100px; -webkit-border-radius: 100px;
} }
.cet-menubar-menu-container .cet-action-item {
background-color: inherit
}

View File

@ -89,6 +89,7 @@ const postSongDataToAPI = async (songInfo, config, data) => {
track: songInfo.title, track: songInfo.title,
duration: songInfo.songDuration, duration: songInfo.songDuration,
artist: songInfo.artist, artist: songInfo.artist,
...(songInfo.album ? { album: songInfo.album } : undefined), // will be undefined if current song is a video
api_key: config.api_key, api_key: config.api_key,
sk: config.session_key, sk: config.session_key,
format: 'json', format: 'json',
@ -157,4 +158,4 @@ const lastfm = async (_win, config) => {
}); });
} }
module.exports = lastfm; module.exports = lastfm;

View File

@ -2,6 +2,7 @@ const { join } = require("path");
const { ipcMain } = require("electron"); const { ipcMain } = require("electron");
const is = require("electron-is"); const is = require("electron-is");
const { convert } = require("html-to-text");
const fetch = require("node-fetch"); const fetch = require("node-fetch");
const { cleanupName } = require("../../providers/song-info"); const { cleanupName } = require("../../providers/song-info");
@ -12,15 +13,14 @@ module.exports = async (win) => {
ipcMain.on("search-genius-lyrics", async (event, extractedSongInfo) => { ipcMain.on("search-genius-lyrics", async (event, extractedSongInfo) => {
const metadata = JSON.parse(extractedSongInfo); const metadata = JSON.parse(extractedSongInfo);
const queryString = `${cleanupName(metadata.artist)} ${cleanupName( event.returnValue = await fetchFromGenius(metadata);
metadata.title
)}`;
event.returnValue = await fetchFromGenius(queryString);
}); });
}; };
const fetchFromGenius = async (queryString) => { const fetchFromGenius = async (metadata) => {
const queryString = `${cleanupName(metadata.artist)} ${cleanupName(
metadata.title
)}`;
let response = await fetch( let response = await fetch(
`https://genius.com/api/search/multi?per_page=5&q=${encodeURI(queryString)}` `https://genius.com/api/search/multi?per_page=5&q=${encodeURI(queryString)}`
); );
@ -46,5 +46,26 @@ const fetchFromGenius = async (queryString) => {
return null; return null;
} }
return await response.text(); const html = await response.text();
const lyrics = convert(html, {
baseElements: {
selectors: ['[class^="Lyrics__Container"]', ".lyrics"],
},
selectors: [
{
selector: "a",
format: "linkFormatter",
},
],
formatters: {
// Remove links by keeping only the content
linkFormatter: (elem, walk, builder) => {
walk(elem.children, builder);
},
},
});
return lyrics;
}; };
module.exports.fetchFromGenius = fetchFromGenius;

View File

@ -17,11 +17,11 @@ module.exports = () => {
let hasLyrics = true; let hasLyrics = true;
const html = ipcRenderer.sendSync( const lyrics = ipcRenderer.sendSync(
"search-genius-lyrics", "search-genius-lyrics",
extractedSongInfo extractedSongInfo
); );
if (!html) { if (!lyrics) {
// Delete previous lyrics if tab is open and couldn't get new lyrics // Delete previous lyrics if tab is open and couldn't get new lyrics
checkLyricsContainer(() => { checkLyricsContainer(() => {
hasLyrics = false; hasLyrics = false;
@ -34,21 +34,6 @@ module.exports = () => {
console.log("Fetched lyrics from Genius"); console.log("Fetched lyrics from Genius");
} }
const wrapper = document.createElement("div");
wrapper.innerHTML = html;
const lyricsSelector1 = wrapper.querySelector(".lyrics");
const lyricsSelector2 = wrapper.querySelector(
'[class^="Lyrics__Container"]'
);
const lyrics = lyricsSelector1
? lyricsSelector1.innerHTML
: lyricsSelector2
? lyricsSelector2.innerHTML
: null;
if (!lyrics) {
return;
}
enableLyricsTab(); enableLyricsTab();
setTabsOnclick(enableLyricsTab); setTabsOnclick(enableLyricsTab);
@ -78,9 +63,12 @@ module.exports = () => {
} }
function setLyrics(lyricsContainer) { function setLyrics(lyricsContainer) {
lyricsContainer.innerHTML = lyricsContainer.innerHTML = `<div id="contents" class="style-scope ytmusic-section-list-renderer description ytmusic-description-shelf-renderer genius-lyrics">
`<div id="contents" class="style-scope ytmusic-section-list-renderer description ytmusic-description-shelf-renderer genius-lyrics"> ${
${hasLyrics ? lyrics : 'Could not retrieve lyrics from genius'} hasLyrics
? lyrics.replace(/(?:\r\n|\r|\n)/g, "<br/>")
: "Could not retrieve lyrics from genius"
}
</div> </div>
<yt-formatted-string class="footer style-scope ytmusic-description-shelf-renderer" style="align-self: baseline"></yt-formatted-string>`; <yt-formatted-string class="footer style-scope ytmusic-description-shelf-renderer" style="align-self: baseline"></yt-formatted-string>`;

View File

@ -7,6 +7,6 @@
} }
.description { .description {
font-size: 1.1vw !important; font-size: clamp(1.4rem, 1.1vmax, 3rem) !important;
text-align: center !important; text-align: center !important;
} }

View File

@ -3,7 +3,6 @@
font-size: 20px; font-size: 20px;
line-height: var(--ytmusic-title-1_-_line-height); line-height: var(--ytmusic-title-1_-_line-height);
font-weight: 500; font-weight: 500;
color: #fff;
--yt-endpoint-color: #fff; --yt-endpoint-color: #fff;
--yt-endpoint-hover-color: #fff; --yt-endpoint-hover-color: #fff;
--yt-endpoint-visited-color: #fff; --yt-endpoint-visited-color: #fff;

View File

@ -1,3 +1,4 @@
.ytmusic-pivot-bar-renderer[tab-id="FEmusic_liked"] { .ytmusic-pivot-bar-renderer[tab-id="FEmusic_liked"],
.sign-in-link {
display: none !important; display: none !important;
} }

View File

@ -0,0 +1,37 @@
const { Menu, app } = require("electron");
const { setApplicationMenu } = require("../../../menu");
module.exports = (win, options, setOptions, togglePip, isInPip) => {
if (isInPip) {
Menu.setApplicationMenu(Menu.buildFromTemplate([
{
label: "App",
submenu: [
{
label: "Exit Picture in Picture",
click: togglePip,
},
{
label: "Always on top",
type: "checkbox",
checked: options.alwaysOnTop,
click: (item) => {
setOptions({ alwaysOnTop: item.checked });
win.setAlwaysOnTop(item.checked);
},
},
{
label: "Restart",
click: () => {
app.relaunch();
app.quit();
},
},
{ role: "quit" },
],
},
]));
} else {
setApplicationMenu(win);
}
};

View File

@ -0,0 +1,109 @@
const path = require("path");
const { app, ipcMain } = require("electron");
const electronLocalshortcut = require("electron-localshortcut");
const { setOptions, isEnabled } = require("../../config/plugins");
const { injectCSS } = require("../utils");
let isInPiP = false;
let originalPosition;
let originalSize;
let originalFullScreen;
let originalMaximized;
let win;
let options;
const pipPosition = () => (options.savePosition && options["pip-position"]) || [10, 10];
const pipSize = () => (options.saveSize && options["pip-size"]) || [450, 275];
const setLocalOptions = (_options) => {
options = { ...options, ..._options };
setOptions("picture-in-picture", _options);
}
const adaptors = [];
const runAdaptors = () => adaptors.forEach(a => a());
if (isEnabled("in-app-menu")) {
let adaptor = require("./adaptors/in-app-menu");
adaptors.push(() => adaptor(win, options, setLocalOptions, togglePiP, isInPiP));
}
const togglePiP = async () => {
isInPiP = !isInPiP;
setLocalOptions({ isInPiP });
if (isInPiP) {
originalFullScreen = win.isFullScreen();
if (originalFullScreen) win.setFullScreen(false);
originalMaximized = win.isMaximized();
if (originalMaximized) win.unmaximize();
originalPosition = win.getPosition();
originalSize = win.getSize();
win.webContents.on("before-input-event", blockShortcutsInPiP);
win.setFullScreenable(false);
runAdaptors();
win.webContents.send("pip-toggle", true);
app.dock?.hide();
win.setVisibleOnAllWorkspaces(true, {
visibleOnFullScreen: true,
});
app.dock?.show();
if (options.alwaysOnTop) {
win.setAlwaysOnTop(true, "screen-saver", 1);
}
} else {
win.webContents.removeListener("before-input-event", blockShortcutsInPiP);
win.setFullScreenable(true);
runAdaptors();
win.webContents.send("pip-toggle", false);
win.setVisibleOnAllWorkspaces(false);
win.setAlwaysOnTop(false);
if (originalFullScreen) win.setFullScreen(true);
if (originalMaximized) win.maximize();
}
const [x, y] = isInPiP ? pipPosition() : originalPosition;
const [w, h] = isInPiP ? pipSize() : originalSize;
win.setPosition(x, y);
win.setSize(w, h);
win.setWindowButtonVisibility?.(!isInPiP);
};
const blockShortcutsInPiP = (event, input) => {
const key = input.key.toLowerCase();
if (key === "f") {
event.preventDefault();
} else if (key === 'escape') {
togglePiP();
event.preventDefault();
};
};
module.exports = (_win, _options) => {
options ??= _options;
win ??= _win;
setLocalOptions({ isInPiP });
injectCSS(win.webContents, path.join(__dirname, "style.css"));
ipcMain.on("picture-in-picture", async () => {
await togglePiP();
});
if (options.hotkey) {
electronLocalshortcut.register(win, options.hotkey, togglePiP);
}
};
module.exports.setOptions = setLocalOptions;

View File

@ -0,0 +1,90 @@
const { ipcRenderer } = require("electron");
const { getSongMenu } = require("../../providers/dom-elements");
const { ElementFromFile, templatePath } = require("../utils");
function $(selector) { return document.querySelector(selector); }
let menu = null;
const pipButton = ElementFromFile(
templatePath(__dirname, "picture-in-picture.html")
);
const observer = new MutationObserver(() => {
if (!menu) {
menu = getSongMenu();
if (!menu) return;
}
if (menu.contains(pipButton)) return;
const menuUrl = $(
'tp-yt-paper-listbox [tabindex="0"] #navigation-endpoint'
)?.href;
if (menuUrl && !menuUrl.includes("watch?")) return;
menu.prepend(pipButton);
});
global.togglePictureInPicture = () => {
ipcRenderer.send("picture-in-picture");
};
const listenForToggle = () => {
const originalExitButton = $(".exit-fullscreen-button");
const clonedExitButton = originalExitButton.cloneNode(true);
clonedExitButton.onclick = () => togglePictureInPicture();
const appLayout = $("ytmusic-app-layout");
const expandMenu = $('#expanding-menu');
const middleControls = $('.middle-controls');
const playerPage = $("ytmusic-player-page");
const togglePlayerPageButton = $(".toggle-player-page-button");
const fullScreenButton = $(".fullscreen-button");
const player = $('#player');
const onPlayerDblClick = player.onDoubleClick_;
ipcRenderer.on('pip-toggle', (_, isPip) => {
if (isPip) {
$(".exit-fullscreen-button").replaceWith(clonedExitButton);
player.onDoubleClick_ = () => {};
expandMenu.onmouseleave = () => middleControls.click();
if (!playerPage.playerPageOpen_) {
togglePlayerPageButton.click();
}
fullScreenButton.click();
appLayout.classList.add("pip");
} else {
$(".exit-fullscreen-button").replaceWith(originalExitButton);
player.onDoubleClick_ = onPlayerDblClick;
expandMenu.onmouseleave = undefined;
originalExitButton.click();
appLayout.classList.remove("pip");
}
});
}
function observeMenu(options) {
document.addEventListener(
"apiLoaded",
() => {
listenForToggle();
const minButton = $(".player-minimize-button");
// remove native listeners
minButton.replaceWith(minButton.cloneNode(true));
$(".player-minimize-button").onclick = () => {
global.togglePictureInPicture();
setTimeout(() => $('#player').click());
};
// allows easily closing the menu by programmatically clicking outside of it
$("#expanding-menu").removeAttribute("no-cancel-on-outside-click");
// TODO: think about wether an additional button in songMenu is needed
observer.observe($("ytmusic-popup-container"), {
childList: true,
subtree: true,
});
},
{ once: true, passive: true }
);
}
module.exports = observeMenu;

View File

@ -0,0 +1,60 @@
const prompt = require("custom-electron-prompt");
const promptOptions = require("../../providers/prompt-options");
const { setOptions } = require("./back.js");
module.exports = (win, options) => [
{
label: "Always on top",
type: "checkbox",
checked: options.alwaysOnTop,
click: (item) => {
setOptions({ alwaysOnTop: item.checked });
win.setAlwaysOnTop(item.checked);
},
},
{
label: "Save window position",
type: "checkbox",
checked: options.savePosition,
click: (item) => {
setOptions({ savePosition: item.checked });
},
},
{
label: "Save window size",
type: "checkbox",
checked: options.saveSize,
click: (item) => {
setOptions({ saveSize: item.checked });
},
},
{
label: "Hotkey",
type: "checkbox",
checked: options.hotkey,
click: async (item) => {
const output = await prompt({
title: "Picture in Picture Hotkey",
label: "Choose a hotkey for toggling Picture in Picture",
type: "keybind",
keybindOptions: [{
value: "hotkey",
label: "Hotkey",
default: options.hotkey
}],
...promptOptions()
}, win)
if (output) {
const { value, accelerator } = output[0];
setOptions({ [value]: accelerator });
item.checked = !!accelerator;
} else {
// Reset checkbox if prompt was canceled
item.checked = !item.checked;
}
},
}
];

View File

@ -0,0 +1,26 @@
/* improve visibility of the player bar elements */
ytmusic-app-layout.pip ytmusic-player-bar svg,
ytmusic-app-layout.pip ytmusic-player-bar .time-info,
ytmusic-app-layout.pip ytmusic-player-bar yt-formatted-string,
ytmusic-app-layout.pip ytmusic-player-bar .yt-formatted-string {
filter: drop-shadow(2px 4px 6px black);
color: white !important;
fill: white !important;
}
/* improve the style of the player bar expanding menu */
ytmusic-app-layout.pip ytmusic-player-expanding-menu {
border-radius: 30px;
background-color: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(5px) brightness(20%);
}
/* fix volumeHud position when both in-app-menu and PiP are active */
.cet-container ytmusic-app-layout.pip #volumeHud {
top: 22px !important;
}
/* disable the video-toggle button when in PiP mode */
ytmusic-app-layout.pip .video-switch-button {
display: none !important;
}

View File

@ -0,0 +1,51 @@
<div
class="style-scope menu-item ytmusic-menu-popup-renderer"
role="option"
tabindex="-1"
aria-disabled="false"
aria-selected="false"
onclick="togglePictureInPicture()"
>
<div
id="navigation-endpoint"
class="yt-simple-endpoint style-scope ytmusic-menu-navigation-item-renderer"
tabindex="-1"
>
<div
class="icon menu-icon style-scope ytmusic-menu-navigation-item-renderer"
>
<svg
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 512 512"
style="enable-background: new 0 0 512 512"
xml:space="preserve"
>
<style type="text/css">
.st0 {
fill: #aaaaaa;
}
</style>
<g id="XMLID_6_">
<path
id="XMLID_11_"
class="st0"
d="M418.5,139.4H232.4v139.8h186.1V139.4z M464.8,46.7H46.3C20.5,46.7,0,68.1,0,93.1v325.9
c0,25.8,21.4,46.3,46.3,46.3h419.4c25.8,0,46.3-20.5,46.3-46.3V93.1C512,67.2,490.6,46.7,464.8,46.7z M464.8,418.9H46.3V92.2h419.4
v326.8H464.8z"
/>
</g>
</svg>
</div>
<div
class="text style-scope ytmusic-menu-navigation-item-renderer"
id="ytmcustom-pip"
>
Picture in picture
</div>
</div>
</div>

View File

@ -85,7 +85,7 @@ function forcePlaybackRate(e) {
} }
module.exports = () => { module.exports = () => {
document.addEventListener('apiLoaded', e => { document.addEventListener('apiLoaded', () => {
observePopupContainer(); observePopupContainer();
observeVideo(); observeVideo();
setupWheelListener(); setupWheelListener();

View File

@ -1,9 +1,24 @@
const { injectCSS } = require("../utils");
const path = require("path");
/* /*
This is used to determine if plugin is actually active This is used to determine if plugin is actually active
(not if its only enabled in options) (not if its only enabled in options)
*/ */
let enabled = false; let enabled = false;
module.exports = () => enabled = true; const { globalShortcut } = require('electron');
module.exports = (win, options) => {
enabled = true;
injectCSS(win.webContents, path.join(__dirname, "volume-hud.css"));
if (options.globalShortcuts?.volumeUp) {
globalShortcut.register((options.globalShortcuts.volumeUp), () => win.webContents.send('changeVolume', true));
}
if (options.globalShortcuts?.volumeDown) {
globalShortcut.register((options.globalShortcuts.volumeDown), () => win.webContents.send('changeVolume', false));
}
}
module.exports.enabled = () => enabled; module.exports.enabled = () => enabled;

View File

@ -1,22 +1,25 @@
const { ipcRenderer } = require("electron"); const { ipcRenderer } = require("electron");
const { globalShortcut } = require('@electron/remote');
const { setOptions, setMenuOptions, isEnabled } = require("../../config/plugins"); const { setOptions, setMenuOptions, isEnabled } = require("../../config/plugins");
function $(selector) { return document.querySelector(selector); } function $(selector) { return document.querySelector(selector); }
let api;
module.exports = (options) => { let api, options;
module.exports = (_options) => {
options = _options;
document.addEventListener('apiLoaded', e => { document.addEventListener('apiLoaded', e => {
api = e.detail; api = e.detail;
firstRun(options); ipcRenderer.on('changeVolume', (_, toIncrease) => changeVolume(toIncrease));
ipcRenderer.on('setVolume', (_, value) => setVolume(value));
firstRun();
}, { once: true, passive: true }) }, { once: true, passive: true })
}; };
module.exports.moveVolumeHud = moveVolumeHud; module.exports.moveVolumeHud = moveVolumeHud;
/** Restore saved volume and setup tooltip */ /** Restore saved volume and setup tooltip */
function firstRun(options) { function firstRun() {
if (typeof options.savedVolume === "number") { if (typeof options.savedVolume === "number") {
// Set saved volume as tooltip // Set saved volume as tooltip
setTooltip(options.savedVolume); setTooltip(options.savedVolume);
@ -26,16 +29,14 @@ function firstRun(options) {
} }
} }
setupPlaybar(options); setupPlaybar();
setupLocalArrowShortcuts(options); setupLocalArrowShortcuts();
setupGlobalShortcuts(options);
const noVid = $("#main-panel")?.computedStyleMap().get("display").value === "none"; const noVid = $("#main-panel")?.computedStyleMap().get("display").value === "none";
injectVolumeHud(noVid); injectVolumeHud(noVid);
if (!noVid) { if (!noVid) {
setupVideoPlayerOnwheel(options); setupVideoPlayerOnwheel();
if (!isEnabled('video-toggle')) { if (!isEnabled('video-toggle')) {
//video-toggle handles hud positioning on its own //video-toggle handles hud positioning on its own
const videoMode = () => api.getPlayerResponse().videoDetails?.musicVideoType !== 'MUSIC_VIDEO_TYPE_ATV'; const videoMode = () => api.getPlayerResponse().videoDetails?.musicVideoType !== 'MUSIC_VIDEO_TYPE_ATV';
@ -45,23 +46,21 @@ function firstRun(options) {
// Change options from renderer to keep sync // Change options from renderer to keep sync
ipcRenderer.on("setOptions", (_event, newOptions = {}) => { ipcRenderer.on("setOptions", (_event, newOptions = {}) => {
for (option in newOptions) { Object.assign(options, newOptions)
options[option] = newOptions[option];
}
setMenuOptions("precise-volume", options); setMenuOptions("precise-volume", options);
}); });
} }
function injectVolumeHud(noVid) { function injectVolumeHud(noVid) {
if (noVid) { if (noVid) {
const position = "top: 18px; right: 60px; z-index: 999; position: absolute;"; const position = "top: 18px; right: 60px;";
const mainStyle = "font-size: xx-large; padding: 10px; transition: opacity 1s; pointer-events: none;"; const mainStyle = "font-size: xx-large;";
$(".center-content.ytmusic-nav-bar").insertAdjacentHTML("beforeend", $(".center-content.ytmusic-nav-bar").insertAdjacentHTML("beforeend",
`<span id="volumeHud" style="${position + mainStyle}"></span>`) `<span id="volumeHud" style="${position + mainStyle}"></span>`)
} else { } else {
const position = `top: 10px; left: 10px; z-index: 999; position: absolute;`; const position = `top: 10px; left: 10px;`;
const mainStyle = "font-size: xxx-large; padding: 10px; transition: opacity 0.6s; webkit-text-stroke: 1px black; font-weight: 600; pointer-events: none;"; const mainStyle = "font-size: xxx-large; webkit-text-stroke: 1px black; font-weight: 600;";
$("#song-video").insertAdjacentHTML('afterend', $("#song-video").insertAdjacentHTML('afterend',
`<span id="volumeHud" style="${position + mainStyle}"></span>`) `<span id="volumeHud" style="${position + mainStyle}"></span>`)
@ -98,22 +97,22 @@ function showVolumeHud(volume) {
} }
/** Add onwheel event to video player */ /** Add onwheel event to video player */
function setupVideoPlayerOnwheel(options) { function setupVideoPlayerOnwheel() {
$("#main-panel").addEventListener("wheel", event => { $("#main-panel").addEventListener("wheel", event => {
event.preventDefault(); event.preventDefault();
// Event.deltaY < 0 means wheel-up // Event.deltaY < 0 means wheel-up
changeVolume(event.deltaY < 0, options); changeVolume(event.deltaY < 0);
}); });
} }
function saveVolume(volume, options) { function saveVolume(volume) {
options.savedVolume = volume; options.savedVolume = volume;
writeOptions(options); writeOptions();
} }
//without this function it would rewrite config 20 time when volume change by 20 //without this function it would rewrite config 20 time when volume change by 20
let writeTimeout; let writeTimeout;
function writeOptions(options) { function writeOptions() {
if (writeTimeout) clearTimeout(writeTimeout); if (writeTimeout) clearTimeout(writeTimeout);
writeTimeout = setTimeout(() => { writeTimeout = setTimeout(() => {
@ -123,13 +122,13 @@ function writeOptions(options) {
} }
/** Add onwheel event to play bar and also track if play bar is hovered*/ /** Add onwheel event to play bar and also track if play bar is hovered*/
function setupPlaybar(options) { function setupPlaybar() {
const playerbar = $("ytmusic-player-bar"); const playerbar = $("ytmusic-player-bar");
playerbar.addEventListener("wheel", event => { playerbar.addEventListener("wheel", event => {
event.preventDefault(); event.preventDefault();
// Event.deltaY < 0 means wheel-up // Event.deltaY < 0 means wheel-up
changeVolume(event.deltaY < 0, options); changeVolume(event.deltaY < 0);
}); });
// Keep track of mouse position for showVolumeSlider() // Keep track of mouse position for showVolumeSlider()
@ -141,11 +140,11 @@ function setupPlaybar(options) {
playerbar.classList.remove("on-hover"); playerbar.classList.remove("on-hover");
}); });
setupSliderObserver(options); setupSliderObserver();
} }
/** Save volume + Update the volume tooltip when volume-slider is manually changed */ /** Save volume + Update the volume tooltip when volume-slider is manually changed */
function setupSliderObserver(options) { function setupSliderObserver() {
const sliderObserver = new MutationObserver(mutations => { const sliderObserver = new MutationObserver(mutations => {
for (const mutation of mutations) { for (const mutation of mutations) {
// This checks that volume-slider was manually set // This checks that volume-slider was manually set
@ -153,7 +152,7 @@ function setupSliderObserver(options) {
(typeof options.savedVolume !== "number" || Math.abs(options.savedVolume - mutation.target.value) > 4)) { (typeof options.savedVolume !== "number" || Math.abs(options.savedVolume - mutation.target.value) > 4)) {
// Diff>4 means it was manually set // Diff>4 means it was manually set
setTooltip(mutation.target.value); setTooltip(mutation.target.value);
saveVolume(mutation.target.value, options); saveVolume(mutation.target.value);
} }
} }
}); });
@ -165,32 +164,39 @@ function setupSliderObserver(options) {
}); });
} }
/** if (toIncrease = false) then volume decrease */ function setVolume(value) {
function changeVolume(toIncrease, options) { api.setVolume(value);
// Apply volume change if valid
const steps = Number(options.steps || 1);
api.setVolume(toIncrease ?
Math.min(api.getVolume() + steps, 100) :
Math.max(api.getVolume() - steps, 0));
// Save the new volume // Save the new volume
saveVolume(api.getVolume(), options); saveVolume(value);
// change slider position (important) // change slider position (important)
updateVolumeSlider(options); updateVolumeSlider();
// Change tooltips to new value // Change tooltips to new value
setTooltip(options.savedVolume); setTooltip(value);
// Show volume slider // Show volume slider
showVolumeSlider(); showVolumeSlider();
// Show volume HUD // Show volume HUD
showVolumeHud(options.savedVolume); showVolumeHud(value);
} }
function updateVolumeSlider(options) { /** if (toIncrease = false) then volume decrease */
function changeVolume(toIncrease) {
// Apply volume change if valid
const steps = Number(options.steps || 1);
setVolume(toIncrease ?
Math.min(api.getVolume() + steps, 100) :
Math.max(api.getVolume() - steps, 0));
}
function updateVolumeSlider() {
// Slider value automatically rounds to multiples of 5 // Slider value automatically rounds to multiples of 5
$("#volume-slider").value = options.savedVolume > 0 && options.savedVolume < 5 ? for (const slider of ["#volume-slider", "#expand-volume-slider"]) {
5 : options.savedVolume; $(slider).value =
options.savedVolume > 0 && options.savedVolume < 5
? 5
: options.savedVolume;
}
} }
let volumeHoverTimeoutID; let volumeHoverTimeoutID;
@ -226,26 +232,17 @@ function setTooltip(volume) {
} }
} }
function setupGlobalShortcuts(options) { function setupLocalArrowShortcuts() {
if (options.globalShortcuts.volumeUp) {
globalShortcut.register((options.globalShortcuts.volumeUp), () => changeVolume(true, options));
}
if (options.globalShortcuts.volumeDown) {
globalShortcut.register((options.globalShortcuts.volumeDown), () => changeVolume(false, options));
}
}
function setupLocalArrowShortcuts(options) {
if (options.arrowsShortcut) { if (options.arrowsShortcut) {
window.addEventListener('keydown', (event) => { window.addEventListener('keydown', (event) => {
switch (event.code) { switch (event.code) {
case "ArrowUp": case "ArrowUp":
event.preventDefault(); event.preventDefault();
changeVolume(true, options); changeVolume(true);
break; break;
case "ArrowDown": case "ArrowDown":
event.preventDefault(); event.preventDefault();
changeVolume(false, options); changeVolume(false);
break; break;
} }
}); });

View File

@ -1,4 +1,3 @@
const { ipcRenderer } = require("electron");
const is = require("electron-is"); const is = require("electron-is");
let ignored = { let ignored = {

View File

@ -0,0 +1,11 @@
#volumeHud {
z-index: 999;
position: absolute;
transition: opacity 0.6s;
pointer-events: none;
padding: 10px;
}
ytmusic-player[player-ui-state_="MINIPLAYER"] #volumeHud {
top: 0 !important;
}

View File

@ -0,0 +1,15 @@
const { ipcMain, dialog } = require("electron");
module.exports = () => {
ipcMain.handle('qualityChanger', async (_, qualityLabels, currentIndex) => {
return await dialog.showMessageBox({
type: "question",
buttons: qualityLabels,
defaultId: currentIndex,
title: "Choose Video Quality",
message: "Choose Video Quality:",
detail: `Current Quality: ${qualityLabels[currentIndex]}`,
cancelId: -1
})
})
};

View File

@ -1,5 +1,5 @@
const { ElementFromFile, templatePath } = require("../utils"); const { ElementFromFile, templatePath } = require("../utils");
const { dialog } = require('@electron/remote'); const { ipcRenderer } = require("electron");
function $(selector) { return document.querySelector(selector); } function $(selector) { return document.querySelector(selector); }
@ -18,24 +18,17 @@ function setup(event) {
$('.top-row-buttons.ytmusic-player').prepend(qualitySettingsButton); $('.top-row-buttons.ytmusic-player').prepend(qualitySettingsButton);
qualitySettingsButton.onclick = function chooseQuality() { qualitySettingsButton.onclick = function chooseQuality() {
if (api.getPlayerState() === 2) api.playVideo(); setTimeout(() => $('#player').click());
else if (api.getPlayerState() === 1) api.pauseVideo();
const currentIndex = api.getAvailableQualityLevels().indexOf(api.getPlaybackQuality()) const qualityLevels = api.getAvailableQualityLevels();
dialog.showMessageBox({ const currentIndex = qualityLevels.indexOf(api.getPlaybackQuality());
type: "question",
buttons: api.getAvailableQualityLabels(), ipcRenderer.invoke('qualityChanger', api.getAvailableQualityLabels(), currentIndex).then(promise => {
defaultId: currentIndex,
title: "Choose Video Quality",
message: "Choose Video Quality:",
detail: `Current Quality: ${api.getAvailableQualityLabels()[currentIndex]}`,
cancelId: -1
}).then((promise) => {
if (promise.response === -1) return; if (promise.response === -1) return;
const newQuality = api.getAvailableQualityLevels()[promise.response]; const newQuality = qualityLevels[promise.response];
api.setPlaybackQualityRange(newQuality); api.setPlaybackQualityRange(newQuality);
api.setPlaybackQuality(newQuality) api.setPlaybackQuality(newQuality)
}) });
} }
} }

View File

@ -2,6 +2,7 @@ const mpris = require("mpris-service");
const { ipcMain } = require("electron"); const { ipcMain } = require("electron");
const registerCallback = require("../../providers/song-info"); const registerCallback = require("../../providers/song-info");
const getSongControls = require("../../providers/song-controls"); const getSongControls = require("../../providers/song-controls");
const config = require("../../config");
function setupMPRIS() { function setupMPRIS() {
const player = mpris({ const player = mpris({
@ -19,7 +20,7 @@ function setupMPRIS() {
function registerMPRIS(win) { function registerMPRIS(win) {
const songControls = getSongControls(win); const songControls = getSongControls(win);
const { playPause, next, previous } = songControls; const { playPause, next, previous, volumeMinus10, volumePlus10 } = songControls;
try { try {
const secToMicro = n => Math.round(Number(n) * 1e6); const secToMicro = n => Math.round(Number(n) * 1e6);
const microToSec = n => Math.round(Number(n) / 1e6); const microToSec = n => Math.round(Number(n) / 1e6);
@ -34,6 +35,35 @@ function registerMPRIS(win) {
let currentSeconds = 0; let currentSeconds = 0;
ipcMain.on('timeChanged', (_, t) => currentSeconds = t); ipcMain.on('timeChanged', (_, t) => currentSeconds = t);
let currentLoopStatus = undefined;
let manuallySwitchingStatus = false;
ipcMain.on("repeatChanged", (_, mode) => {
if (manuallySwitchingStatus)
return;
if (mode === "Repeat off")
currentLoopStatus = "None";
else if (mode === "Repeat one")
currentLoopStatus = "Track";
else if (mode === "Repeat all")
currentLoopStatus = "Playlist";
player.loopStatus = currentLoopStatus;
});
player.on("loopStatus", (status) => {
// switchRepeat cycles between states in that order
const switches = ["None", "Playlist", "Track"];
const currentIndex = switches.indexOf(currentLoopStatus);
const targetIndex = switches.indexOf(status);
// Get a delta in the range [0,2]
const delta = (targetIndex - currentIndex + 3) % 3;
manuallySwitchingStatus = true;
songControls.switchRepeat(delta);
manuallySwitchingStatus = false;
})
player.getPosition = () => secToMicro(currentSeconds) player.getPosition = () => secToMicro(currentSeconds)
player.on("raise", () => { player.on("raise", () => {
@ -53,21 +83,45 @@ function registerMPRIS(win) {
playPause() playPause()
} }
}); });
player.on("playpause", () => {
player.playbackStatus = player.playbackStatus === 'Playing' ? "Paused" : "Playing";
playPause();
});
player.on("playpause", playPause);
player.on("next", next); player.on("next", next);
player.on("previous", previous); player.on("previous", previous);
player.on('seek', seekBy); player.on('seek', seekBy);
player.on('position', seekTo); player.on('position', seekTo);
registerCallback(songInfo => { ipcMain.on('volumeChanged', (_, value) => {
if (player) { player.volume = value;
const data = { });
'mpris:length': secToMicro(songInfo.songDuration), player.on('volume', (newVolume) => {
'mpris:artUrl': songInfo.imageSrc, if (config.plugins.isEnabled('precise-volume')) {
'xesam:title': songInfo.title, // With precise volume we can set the volume to the exact value.
'xesam:artist': [songInfo.artist], win.webContents.send('setVolume', newVolume)
} else {
// With keyboard shortcuts we can only change the volume in increments of 10, so round it.
const deltaVolume = Math.round((newVolume - player.volume) / 10);
if (deltaVolume > 0) {
for (let i = 0; i < deltaVolume; i++)
volumePlus10();
} else {
for (let i = 0; i < -deltaVolume; i++)
volumeMinus10();
}
}
});
registerCallback(songInfo => {
if (player) {
const data = {
'mpris:length': secToMicro(songInfo.songDuration),
'mpris:artUrl': songInfo.imageSrc,
'xesam:title': songInfo.title,
'xesam:artist': [songInfo.artist],
'mpris:trackid': '/' 'mpris:trackid': '/'
}; };
if (songInfo.album) data['xesam:album'] = songInfo.album; if (songInfo.album) data['xesam:album'] = songInfo.album;

View File

@ -3,7 +3,7 @@ const hark = require("hark/hark.bundle.js");
module.exports = () => { module.exports = () => {
let isSilent = false; let isSilent = false;
document.addEventListener("apiLoaded", (apiEvent) => { document.addEventListener("apiLoaded", () => {
const video = document.querySelector("video"); const video = document.querySelector("video");
const speechEvents = hark(video, { const speechEvents = hark(video, {
threshold: -100, // dB (-100 = absolute silence, 0 = loudest) threshold: -100, // dB (-100 = absolute silence, 0 = loudest)

View File

@ -1,3 +1,5 @@
const { test, expect } = require("@playwright/test");
const { sortSegments } = require("../segments"); const { sortSegments } = require("../segments");
test("Segment sorting", () => { test("Segment sorting", () => {

View File

@ -32,9 +32,16 @@ module.exports.listenAction = (channel, callback) => {
return ipcMain.on(channel, callback); return ipcMain.on(channel, callback);
}; };
module.exports.fileExists = (path, callbackIfExists) => { module.exports.fileExists = (
path,
callbackIfExists,
callbackIfError = undefined
) => {
fs.access(path, fs.F_OK, (err) => { fs.access(path, fs.F_OK, (err) => {
if (err) { if (err) {
if (callbackIfError) {
callbackIfError();
}
return; return;
} }
@ -42,15 +49,22 @@ module.exports.fileExists = (path, callbackIfExists) => {
}); });
}; };
const cssToInject = new Map();
module.exports.injectCSS = (webContents, filepath, cb = undefined) => { module.exports.injectCSS = (webContents, filepath, cb = undefined) => {
webContents.on("did-finish-load", async () => { if (!cssToInject.size) setupCssInjection(webContents);
await webContents.insertCSS(fs.readFileSync(filepath, "utf8"));
if (cb) { cssToInject.set(filepath, cb);
cb();
}
});
}; };
const setupCssInjection = (webContents) => {
webContents.on("did-finish-load", () => {
cssToInject.forEach(async (cb, filepath) => {
await webContents.insertCSS(fs.readFileSync(filepath, "utf8"));
cb?.();
})
});
}
module.exports.getAllPlugins = () => { module.exports.getAllPlugins = () => {
const isDirectory = (source) => fs.lstatSync(source).isDirectory(); const isDirectory = (source) => fs.lstatSync(source).isDirectory();
return fs return fs

View File

@ -4,7 +4,7 @@ const path = require("path");
module.exports = (win, options) => { module.exports = (win, options) => {
if (options.forceHide) { if (options.forceHide) {
injectCSS(win.webContents, path.join(__dirname, "force-hide.css")); injectCSS(win.webContents, path.join(__dirname, "force-hide.css"));
} else { } else if (!options.mode || options.mode === "custom") {
injectCSS(win.webContents, path.join(__dirname, "button-switcher.css")); injectCSS(win.webContents, path.join(__dirname, "button-switcher.css"));
} }
}; };

View File

@ -75,3 +75,8 @@
transform: translateX(0); transform: translateX(0);
transition: transform 300ms; transition: transform 300ms;
} }
/* disable the native toggler */
#av-id {
display: none;
}

View File

@ -14,9 +14,24 @@ const switchButtonDiv = ElementFromFile(
module.exports = (_options) => { module.exports = (_options) => {
if (_options.forceHide) return; if (_options.forceHide) return;
options = _options; switch (_options.mode) {
document.addEventListener('apiLoaded', setup, { once: true, passive: true }); case "native": {
} $("ytmusic-player-page").setAttribute("has-av-switcher");
$("ytmusic-player").setAttribute("has-av-switcher");
return;
}
case "disabled": {
$("ytmusic-player-page").removeAttribute("has-av-switcher");
$("ytmusic-player").removeAttribute("has-av-switcher");
return;
}
default:
case "custom": {
options = _options;
document.addEventListener("apiLoaded", setup, { once: true, passive: true });
}
}
};
function setup(e) { function setup(e) {
api = e.detail; api = e.detail;
@ -25,8 +40,6 @@ function setup(e) {
$('ytmusic-player-page').prepend(switchButtonDiv); $('ytmusic-player-page').prepend(switchButtonDiv);
$('#song-image.ytmusic-player').style.display = "block";
if (options.hideVideo) { if (options.hideVideo) {
$('.video-switch-button-checkbox').checked = false; $('.video-switch-button-checkbox').checked = false;
changeDisplay(false); changeDisplay(false);
@ -50,7 +63,10 @@ function setup(e) {
function changeDisplay(showVideo) { function changeDisplay(showVideo) {
player.style.margin = showVideo ? '' : 'auto 0px'; player.style.margin = showVideo ? '' : 'auto 0px';
player.setAttribute('playback-mode', showVideo ? 'OMV_PREFERRED' : 'ATV_PREFERRED'); player.setAttribute('playback-mode', showVideo ? 'OMV_PREFERRED' : 'ATV_PREFERRED');
$('#song-video.ytmusic-player').style.display = showVideo ? 'unset' : 'none';
$('#song-video.ytmusic-player').style.display = showVideo ? 'block' : 'none';
$('#song-image').style.display = showVideo ? 'none' : 'block';
if (showVideo && !video.style.top) { if (showVideo && !video.style.top) {
video.style.top = `${(player.clientHeight - video.clientHeight) / 2}px`; video.style.top = `${(player.clientHeight - video.clientHeight) / 2}px`;
} }

View File

@ -1,6 +1,38 @@
const { setMenuOptions } = require("../../config/plugins"); const { setMenuOptions } = require("../../config/plugins");
module.exports = (win, options) => [ module.exports = (win, options) => [
{
label: "Mode",
submenu: [
{
label: "Custom toggle",
type: "radio",
checked: options.mode === 'custom',
click: () => {
options.mode = 'custom';
setMenuOptions("video-toggle", options);
}
},
{
label: "Native toggle",
type: "radio",
checked: options.mode === 'native',
click: () => {
options.mode = 'native';
setMenuOptions("video-toggle", options);
}
},
{
label: "Disabled",
type: "radio",
checked: options.mode === 'disabled',
click: () => {
options.mode = 'disabled';
setMenuOptions("video-toggle", options);
}
},
]
},
{ {
label: "Force Remove Video Tab", label: "Force Remove Video Tab",
type: "checkbox", type: "checkbox",

View File

@ -1,25 +1,34 @@
const path = require("path"); require("./providers/front-logger")();
const remote = require('@electron/remote');
const config = require("./config"); const config = require("./config");
const { fileExists } = require("./plugins/utils"); const { fileExists } = require("./plugins/utils");
const setupFrontLogger = require("./providers/front-logger");
const setupSongInfo = require("./providers/song-info-front"); const setupSongInfo = require("./providers/song-info-front");
const { setupSongControls } = require("./providers/song-controls-front"); const { setupSongControls } = require("./providers/song-controls-front");
const { ipcRenderer } = require("electron");
const plugins = config.plugins.getEnabled(); const plugins = config.plugins.getEnabled();
let api; let api;
plugins.forEach(([plugin, options]) => { plugins.forEach(async ([plugin, options]) => {
const preloadPath = path.join(__dirname, "plugins", plugin, "preload.js"); const preloadPath = await ipcRenderer.invoke(
"getPath",
__dirname,
"plugins",
plugin,
"preload.js"
);
fileExists(preloadPath, () => { fileExists(preloadPath, () => {
const run = require(preloadPath); const run = require(preloadPath);
run(options); run(options);
}); });
const actionPath = path.join(__dirname, "plugins", plugin, "actions.js"); const actionPath = await ipcRenderer.invoke(
"getPath",
__dirname,
"plugins",
plugin,
"actions.js"
);
fileExists(actionPath, () => { fileExists(actionPath, () => {
const actions = require(actionPath).actions || {}; const actions = require(actionPath).actions || {};
@ -32,8 +41,14 @@ plugins.forEach(([plugin, options]) => {
}); });
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
plugins.forEach(([plugin, options]) => { plugins.forEach(async ([plugin, options]) => {
const pluginPath = path.join(__dirname, "plugins", plugin, "front.js"); const pluginPath = await ipcRenderer.invoke(
"getPath",
__dirname,
"plugins",
plugin,
"front.js"
);
fileExists(pluginPath, () => { fileExists(pluginPath, () => {
const run = require(pluginPath); const run = require(pluginPath);
run(options); run(options);
@ -49,12 +64,8 @@ document.addEventListener("DOMContentLoaded", () => {
// inject song-controls // inject song-controls
setupSongControls(); setupSongControls();
// inject front logger
setupFrontLogger();
// Add action for reloading // Add action for reloading
global.reload = () => global.reload = () => ipcRenderer.send('reload');
remote.getCurrentWindow().webContents.loadURL(config.get("url"));
// Blocks the "Are You Still There?" popup by setting the last active time to Date.now every 15min // Blocks the "Are You Still There?" popup by setting the last active time to Date.now every 15min
setInterval(() => window._lact = Date.now(), 900000); setInterval(() => window._lact = Date.now(), 900000);
@ -83,9 +94,17 @@ function onApiLoaded() {
// Remove upgrade button // Remove upgrade button
if (config.get("options.removeUpgradeButton")) { if (config.get("options.removeUpgradeButton")) {
const upgradeButtton = document.querySelector('ytmusic-pivot-bar-item-renderer[tab-id="SPunlimited"]') const upgradeButton = document.querySelector('ytmusic-pivot-bar-item-renderer[tab-id="SPunlimited"]')
if (upgradeButtton) { if (upgradeButton) {
upgradeButtton.style.display = "none"; upgradeButton.style.display = "none";
}
}
// Force show like buttons
if (config.get("options.ForceShowLikeButtons")) {
const likeButtons = document.querySelector('ytmusic-like-button-renderer')
if (likeButtons) {
likeButtons.style.display = 'inherit';
} }
} }
} }

View File

@ -1,6 +1,23 @@
const app = require("electron").app || require('@electron/remote').app; const path = require("path");
const is = require("electron-is");
const { app, BrowserWindow, ipcMain, ipcRenderer } = require("electron");
const config = require("../config");
module.exports.restart = () => { module.exports.restart = () => {
app.relaunch(); is.main() ? restart() : ipcRenderer.send('restart');
app.exit();
}; };
module.exports.setupAppControls = () => {
ipcMain.on('restart', restart);
ipcMain.handle('getDownloadsFolder', () => app.getPath("downloads"));
ipcMain.on('reload', () => BrowserWindow.getFocusedWindow().webContents.loadURL(config.get("url")));
ipcMain.handle('getPath', (_, ...args) => path.join(...args));
}
function restart() {
app.relaunch({ execPath: process.env.PORTABLE_EXECUTABLE_FILE });
// execPath will be undefined if not running portable app, resulting in default behavior
app.quit();
}

View File

@ -1,17 +1,18 @@
const path = require("path"); const path = require("path");
const is = require("electron-is"); const is = require("electron-is");
const { isEnabled } = require("../config/plugins");
const iconPath = path.join(__dirname, "..", "assets", "youtube-music-tray.png"); const iconPath = path.join(__dirname, "..", "assets", "youtube-music-tray.png");
const customTitlebarPath = path.join(__dirname, "prompt-custom-titlebar.js"); const customTitlebarPath = path.join(__dirname, "prompt-custom-titlebar.js");
const promptOptions = is.macOS() ? { const promptOptions = !is.macOS() && isEnabled("in-app-menu") ? {
customStylesheet: "dark",
icon: iconPath
} : {
customStylesheet: "dark", customStylesheet: "dark",
// The following are used for custom titlebar // The following are used for custom titlebar
frame: false, frame: false,
customScript: customTitlebarPath, customScript: customTitlebarPath,
} : {
customStylesheet: "dark",
icon: iconPath
}; };
module.exports = () => promptOptions; module.exports = () => promptOptions;

View File

@ -12,7 +12,7 @@ module.exports = (win) => {
// Playback // Playback
previous: () => pressKey(win, "k"), previous: () => pressKey(win, "k"),
next: () => pressKey(win, "j"), next: () => pressKey(win, "j"),
playPause: () => pressKey(win, "space"), playPause: () => pressKey(win, ";"),
like: () => pressKey(win, "+"), like: () => pressKey(win, "+"),
dislike: () => pressKey(win, "_"), dislike: () => pressKey(win, "_"),
go10sBack: () => pressKey(win, "h"), go10sBack: () => pressKey(win, "h"),
@ -20,7 +20,10 @@ module.exports = (win) => {
go1sBack: () => pressKey(win, "h", ["shift"]), go1sBack: () => pressKey(win, "h", ["shift"]),
go1sForward: () => pressKey(win, "l", ["shift"]), go1sForward: () => pressKey(win, "l", ["shift"]),
shuffle: () => pressKey(win, "s"), shuffle: () => pressKey(win, "s"),
switchRepeat: () => pressKey(win, "r"), switchRepeat: (n = 1) => {
for (let i = 0; i < n; i++)
pressKey(win, "r");
},
// General // General
volumeMinus10: () => pressKey(win, "-"), volumeMinus10: () => pressKey(win, "-"),
volumePlus10: () => pressKey(win, "="), volumePlus10: () => pressKey(win, "="),

View File

@ -22,6 +22,8 @@ module.exports = () => {
if (config.plugins.isEnabled('tuna-obs') || if (config.plugins.isEnabled('tuna-obs') ||
(is.linux() && config.plugins.isEnabled('shortcuts'))) { (is.linux() && config.plugins.isEnabled('shortcuts'))) {
setupTimeChangeListener(); setupTimeChangeListener();
setupRepeatChangeListener();
setupVolumeChangeListener(apiEvent.detail);
} }
const video = $('video'); const video = $('video');
// name = "dataloaded" and abit later "dataupdated" // name = "dataloaded" and abit later "dataupdated"
@ -63,3 +65,21 @@ function setupTimeChangeListener() {
}); });
progressObserver.observe($('#progress-bar'), { attributeFilter: ["value"] }) progressObserver.observe($('#progress-bar'), { attributeFilter: ["value"] })
} }
function setupRepeatChangeListener() {
const repeatObserver = new MutationObserver(mutations => {
ipcRenderer.send('repeatChanged', mutations[0].target.title);
});
repeatObserver.observe($('#right-controls .repeat'), { attributeFilter: ["title"] });
// Emit the initial value as well; as it's persistent between launches.
ipcRenderer.send('repeatChanged', $('#right-controls .repeat').title);
}
function setupVolumeChangeListener(api) {
$('video').addEventListener('volumechange', (_) => {
ipcRenderer.send('volumeChanged', api.getVolume());
});
// Emit the initial value as well; as it's persistent between launches.
ipcRenderer.send('volumeChanged', api.getVolume());
}

View File

@ -33,6 +33,10 @@ You can check out the [latest release](https://github.com/th-ch/youtube-music/re
Install the `youtube-music-bin` package from the AUR. For AUR installation instructions, take a look at this [wiki page](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages). Install the `youtube-music-bin` package from the AUR. For AUR installation instructions, take a look at this [wiki page](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
### MacOS
If you get an error "is damaged and cant be opened." when launching the app, run `xattr -cr /Applications/YouTube\ Music.app` in Terminal.
## Available plugins: ## Available plugins:
- **Ad Blocker**: Block all ads and tracking out of the box - **Ad Blocker**: Block all ads and tracking out of the box
@ -41,6 +45,8 @@ Install the `youtube-music-bin` package from the AUR. For AUR installation instr
- **Blur Nav Bar**: makes navigation bar transparent and blurry - **Blur Nav Bar**: makes navigation bar transparent and blurry
- **Bypass age restrictions**: bypass YouTube's age verification
- **Disable Autoplay**: Makes every song start in "paused" mode - **Disable Autoplay**: Makes every song start in "paused" mode
- [**Discord**](https://discord.com/): Show your friends what you listen to with [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png) - [**Discord**](https://discord.com/): Show your friends what you listen to with [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
@ -63,6 +69,8 @@ Install the `youtube-music-bin` package from the AUR. For AUR installation instr
- **Notifications**: Display a notification when a song starts playing ([interactive notifications](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) are available on windows) - **Notifications**: Display a notification when a song starts playing ([interactive notifications](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) are available on windows)
- **Picture in picture**: allows to switch the app to picture-in-picture mode
- **Playback Speed**: Listen fast, listen slow! [Adds a slider that controls song speed](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png) - **Playback Speed**: Listen fast, listen slow! [Adds a slider that controls song speed](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
- **Precise Volume**: Control the volume precisely using mousewheel/hotkeys, with a custom hud and customizable volume steps - **Precise Volume**: Control the volume precisely using mousewheel/hotkeys, with a custom hud and customizable volume steps
@ -81,7 +89,7 @@ Install the `youtube-music-bin` package from the AUR. For AUR installation instr
- **Tuna-OBS**: Integration with [OBS](https://obsproject.com/)'s plugin [Tuna](https://obsproject.com/forum/resources/tuna.843/) - **Tuna-OBS**: Integration with [OBS](https://obsproject.com/)'s plugin [Tuna](https://obsproject.com/forum/resources/tuna.843/)
- **Video Toggle**: Adds a button to switch between Video/Song mode. can also optionally remove the whole video tab - **Video Toggle**: Adds a [button](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) to switch between Video/Song mode. can also optionally remove the whole video tab
--- ---
@ -89,6 +97,12 @@ Install the `youtube-music-bin` package from the AUR. For AUR installation instr
> If using `Hide Menu` option - you can show the menu with the `alt` key (or `escape` if using the in-app-menu plugin) > If using `Hide Menu` option - you can show the menu with the `alt` key (or `escape` if using the in-app-menu plugin)
## Themes
You can load CSS files to change the look of the application (Options > Visual Tweaks > Themes).
Some predefined themes are available in https://github.com/OceanicSquirrel/themes-for-ytmdesktop-player.
## Dev ## Dev
```sh ```sh

View File

@ -1,30 +0,0 @@
const path = require("path");
const NodeEnvironment = require("jest-environment-node");
const { _electron: electron } = require("playwright");
class TestEnvironment extends NodeEnvironment {
constructor(config) {
super(config);
}
async setup() {
await super.setup();
const appPath = path.resolve(__dirname, "..");
this.global.__APP__ = await electron.launch({ args: [appPath] });
}
async teardown() {
if (this.global.__APP__) {
await this.global.__APP__.close();
}
await super.teardown();
}
runScript(script) {
return super.runScript(script);
}
}
module.exports = TestEnvironment;

View File

@ -1,16 +1,29 @@
/** const path = require("path");
* @jest-environment ./tests/environment
*/
describe("YouTube Music App", () => { const { _electron: electron } = require("playwright");
const app = global.__APP__; const { test, expect } = require("@playwright/test");
test("With default settings, app is launched and visible", async () => { process.env.NODE_ENV = "test";
const window = await app.firstWindow();
const title = await window.title();
expect(title).toEqual("YouTube Music");
const url = window.url(); const appPath = path.resolve(__dirname, "..");
expect(url.startsWith("https://music.youtube.com")).toBe(true);
test("YouTube Music App - With default settings, app is launched and visible", async () => {
const app = await electron.launch({
args: [
"--no-sandbox",
"--disable-gpu",
"--whitelisted-ips=",
"--disable-dev-shm-usage",
appPath,
],
}); });
const window = await app.firstWindow();
const title = await window.title();
expect(title).toEqual("YouTube Music");
const url = window.url();
expect(url.startsWith("https://music.youtube.com")).toBe(true);
await app.close();
}); });

19
tray.js
View File

@ -1,7 +1,8 @@
const path = require("path"); const path = require("path");
const { Menu, nativeImage, Tray } = require("electron"); const { app, Menu, nativeImage, Tray } = require("electron");
const { restart } = require("./providers/app-controls");
const config = require("./config"); const config = require("./config");
const getSongControls = require("./providers/song-controls"); const getSongControls = require("./providers/song-controls");
@ -27,7 +28,13 @@ module.exports.setUpTray = (app, win) => {
if (config.get("options.trayClickPlayPause")) { if (config.get("options.trayClickPlayPause")) {
playPause(); playPause();
} else { } else {
win.isVisible() ? win.hide() : win.show(); if (win.isVisible()) {
win.hide();
app.dock?.hide();
} else {
win.show();
app.dock?.show();
}
} }
}); });
@ -54,16 +61,14 @@ module.exports.setUpTray = (app, win) => {
label: "Show", label: "Show",
click: () => { click: () => {
win.show(); win.show();
app.dock?.show();
}, },
}, },
{ {
label: "Restart App", label: "Restart App",
click: () => { click: restart
app.relaunch();
app.quit();
},
}, },
{ role: "quit" } { role: "quit" },
]; ];
const trayMenu = Menu.buildFromTemplate(template); const trayMenu = Menu.buildFromTemplate(template);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 816 KiB

After

Width:  |  Height:  |  Size: 726 KiB

2508
yarn.lock

File diff suppressed because it is too large Load Diff