Compare commits

...

280 Commits

Author SHA1 Message Date
TC
96b2aab683 Bump version and changelog to 1.17.0 2022-05-16 18:47:00 +02: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
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
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
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
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
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
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
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
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
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
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
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
5cffb6f062 Merge pull request #619 from Araxeus/fix-prompt-options
fix custom titlebar in prompt options
2022-02-24 19:27:56 +01:00
47729130c9 fix custom titlebar in prompt options 2022-02-23 17:15:10 +02:00
TC
ff39ddb277 Update changelog 2022-02-20 19:15:07 +01:00
TC
1d9bfe8ac8 Add automatic changelog 2022-02-20 18:50:12 +01:00
TC
edfa9d2ff5 Re-format package.json 2022-02-20 18:46:50 +01:00
TC
efd2c912a8 Bump version to 1.16.0 2022-02-20 18:37:48 +01:00
2dd208c21b Merge pull request #596 from Araxeus/update-custom-electron-titlebar
update in-app-menu
2022-02-20 18:31:59 +01:00
c7fe81475f Merge pull request #602 from xn-oah/master
Fix clientID
2022-02-20 18:25:25 +01:00
0acd16fd5d Update yarn.lock 2022-02-15 19:01:23 +02:00
107c79f6a8 Merge branch 'master' of https://github.com/xn-oah/youtube-music 2022-02-13 16:02:53 -06:00
6aa789fb6a fixed clientID 2022-02-13 16:02:50 -06:00
94c86491d0 Merge branch 'th-ch:master' into master 2022-02-13 16:00:29 -06:00
a9f5f376d0 fixed clientID 2022-02-13 15:59:53 -06:00
TC
ea35da52c3 Refactor Genius logic into separate util 2022-02-13 22:19:16 +01:00
6cad6871bf fix directory path 2022-02-13 22:02:12 +01:00
TC
f865dfd1b6 Fix https://github.com/th-ch/youtube-music/pull/578#issuecomment-1035517531 2022-02-13 19:56:46 +01:00
37e84d3287 Merge pull request #600 from th-ch/snoretoast-custom-compile
Add snoretoast custom compile script
2022-02-13 19:44:27 +01:00
7192b253eb Merge pull request #591 from Araxeus/update-snoretoast
fix interactive notifications icon + exclude platform specific plugins from build
2022-02-13 19:43:46 +01:00
1b99cc6930 use custom snoretoast on release #600 2022-02-13 20:40:09 +02:00
7f0d62383d Merge pull request #587 from xn-oah/master
Add album title to largeImage and change paused icon
2022-02-13 19:07:43 +01:00
TC
44300e757f Add snoretoast custom compile script 2022-02-13 18:14:57 +01:00
dc928542f8 lighten up menuItem background color 2022-02-11 16:29:11 +02:00
1834e1e938 Merge branch 'th-ch:master' into update-snoretoast 2022-02-11 15:54:13 +02:00
8263918033 add toggle fullscreen to view menu
enables f11 shortcut
2022-02-11 15:49:33 +02:00
247777bcc4 remove remote module from prompt-options 2022-02-11 15:24:42 +02:00
023db03278 Merge branch 'master' into update-custom-electron-titlebar 2022-02-10 23:13:55 +02:00
TC
a3f7eebd14 Bump version 2022-02-10 00:37:35 +01:00
5c7d612e97 Merge pull request #595 from Araxeus/useragent-option
make useragent override optional
2022-02-10 00:33:59 +01:00
465e8e1717 Merge branch 'master' into useragent-option 2022-02-10 00:32:52 +01:00
621c5de357 Merge pull request #588 from Araxeus/fix-album-name
get album name from DOM
2022-02-10 00:15:02 +01:00
TC
4d890c4941 Fix warnings in genius plugin 2022-02-10 00:08:40 +01:00
9b7c1a8d37 Merge pull request #584 from Araxeus/fix-lyrics
fix various lyrics issues
2022-02-10 00:07:33 +01:00
05b877b702 Merge pull request #580 from Araxeus/discord-timeout-time-prompt
discord set inactivity timeout prompt
2022-02-09 23:49:31 +01:00
8268b18eee Merge branch 'master' into discord-timeout-time-prompt 2022-02-09 23:47:01 +01:00
ed15ee92df Merge pull request #578 from Araxeus/single-instance-lock-option
add single instance lock option
2022-02-09 23:44:54 +01:00
c2fdfcca58 Merge pull request #561 from Araxeus/fix-restart-on-config-change
fix "restart app on config change" option
2022-02-09 23:40:57 +01:00
ac54d33fa7 Merge pull request #562 from Araxeus/fix-window-position-save-spam
fix window position save spam
2022-02-09 23:37:56 +01:00
38ffc093c3 Merge branch 'master' into update-custom-electron-titlebar 2022-02-07 20:57:15 +02:00
37c0ceaafe Merge branch 'master' into fix-restart-on-config-change 2022-02-07 17:27:27 +02:00
47f71a6022 Merge branch 'master' into discord-timeout-time-prompt 2022-02-07 17:24:44 +02:00
08a39a59d3 Merge branch 'master' into fix-lyrics 2022-02-07 17:14:20 +02:00
e6e83de89d Merge pull request #583 from Araxeus/fix-adblocker-loading-late
load adblocker sooner
2022-02-07 00:49:21 +01:00
1d1a9f5094 Merge pull request #585 from Araxeus/update-readme
add description of new plugins to readme
2022-02-07 00:45:28 +01:00
a5ba0b1a1a Merge pull request #573 from lazerl0rd/patch-1
Use `center` alignment for lyrics text
2022-02-07 00:43:54 +01:00
d785b9b95b Merge pull request #567 from Araxeus/fix-precise-volume-hud-position
fix precise-volume hud positioning
2022-02-07 00:42:33 +01:00
2f5e0c0038 Merge pull request #565 from Araxeus/update-electron
update electron and dependencies
2022-02-07 00:34:43 +01:00
09bd271df2 update in-app-menu 2022-02-06 03:32:15 +02:00
766dd21cb7 make useragent override optional 2022-02-05 18:24:37 +02:00
fef711549f update electron to v17.0.0 2022-02-05 15:20:14 +02:00
ca624f4df8 Merge branch 'master' into update-electron 2022-02-01 22:56:08 +02:00
8be07bcb7a update dependencies 2022-02-01 22:53:10 +02:00
271d5258ca fix interactive notifications icon 2022-02-01 19:47:01 +02:00
721f733dc4 update electron to 16.0.8 2022-01-31 23:50:38 +02:00
9b8d9c4905 get album name from DOM 2022-01-31 01:02:28 +02:00
543db59a55 fix formatting 2022-01-30 13:15:29 -06:00
e9a670831c Add album title to largeImage and change paused icon 2022-01-30 12:48:15 -06:00
24f694737a add new plugins to readme 2022-01-30 19:12:42 +02:00
fc111e2513 keep footer out of contents div 2022-01-29 18:05:55 +02:00
187f6833f4 Merge branch 'fix-lyrics' of https://github.com/Araxeus/youtube-music into fix-lyrics 2022-01-29 17:58:47 +02:00
b042d0a8ca use regex on cleanupName() 2022-01-29 17:57:39 +02:00
eff0995d78 fix showing old lyrics if new lyrics couldn't be resolved 2022-01-29 17:57:14 +02:00
366c90f71d fix showing old lyrics if new lyrics couldn't be resolved 2022-01-29 17:10:47 +02:00
909036108f reenable lyrics footer 2022-01-29 16:56:55 +02:00
41b9ab4815 lint 2022-01-29 16:35:52 +02:00
60bb5b861d change to new lyrics even if lyrics tab was already selected 2022-01-29 11:35:18 +02:00
900c44d9c0 fix disabled lyrics tab 2022-01-29 11:14:03 +02:00
babc50099c fix lyrics css styling 2022-01-29 11:13:53 +02:00
ea191a3005 load adblocker sooner 2022-01-29 09:37:02 +02:00
02081d8272 fix precise-volume hud positioning 2022-01-27 22:41:55 +02:00
03e27519db discord set inactivity timeout prompt 2022-01-27 22:32:45 +02:00
1248f1c8ec add single instance lock option 2022-01-27 10:00:35 +02:00
766bd378fd Use center alignment for lyrics text
This seems to be the case for most music applications [that I've used] and also provides the benefit of "fixing" how lyrics appear in RTL languages (such as Arabic).
2022-01-26 15:58:50 +00:00
61eb23614a Merge pull request #557 from Araxeus/fix-filepath-error
filenamify playlist folder name
2022-01-23 14:48:10 +01:00
3f606695bf Merge pull request #554 from th-ch/snyk-fix-6b7a813ed44a44be91e81ec320a8fde1
[Snyk] Security upgrade node-fetch from 2.6.6 to 2.6.7 (3.1.1 incompatible)
2022-01-23 14:40:53 +01:00
74b67c3d33 fix restart app on config change option 2022-01-23 00:12:20 +02:00
TC
8dd41cca09 Use v2.6.7 of node-fetch 2022-01-22 20:02:06 +01:00
TC
89ea66ba2b Simplify off-screen check 2022-01-22 19:57:23 +01:00
199d8ba4d7 Merge pull request #548 from Araxeus/offscreen-app-fix
fix app starting offscreen
2022-01-22 19:56:12 +01:00
b0f29dde94 Merge pull request #566 from th-ch/release-mac-arm64
Release Mac arm64
2022-01-22 19:54:40 +01:00
TC
90f4c9383f Release Mac arm64 2022-01-22 19:47:10 +01:00
4c8996096a Merge pull request #553 from arunim2405/master
Build command for Apple (m1) silicon macs
2022-01-22 19:45:57 +01:00
315048722f update electron to 16.0.7 2022-01-22 18:07:47 +02:00
9403804128 fix multiple monitor calculations 2022-01-21 21:58:44 +02:00
28b6d99599 lint 2022-01-21 00:05:05 +02:00
92fc8f325a Merge branch 'fix-window-position-save-spam' of https://github.com/Araxeus/youtube-music into fix-window-position-save-spam 2022-01-21 00:02:14 +02:00
a744a2ebde fix window position save spam 2022-01-21 00:01:14 +02:00
c8f62f6d19 fix window position save spam
(and also window position being forgotten on maximizing)
2022-01-20 23:45:57 +02:00
7fd9d5a971 use -8 insteaf of 0
(often maximized app will have -8,-8 coordinates)
2022-01-19 19:40:58 +02:00
cb920194ce filenamify playlist folder name 2022-01-18 18:32:25 +02:00
dc728786ee fix: package.json & yarn.lock to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-NODEFETCH-2342118
2022-01-17 18:10:50 +00:00
8cfc8d1ba1 support build for arm64 macs 2022-01-17 11:58:52 +05:30
68bd691702 check if window.y is offscreen 2022-01-16 20:02:35 +02:00
44aa62c9c8 Merge pull request #545 from th-ch/snyk-upgrade-d09c6b75ea60c0149b8328cb6bad2f0d
[Snyk] Upgrade custom-electron-titlebar from 3.2.9 to 3.2.10
2022-01-16 17:31:38 +01:00
2ad6c0fcdc Merge pull request #551 from Araxeus/fix-linux-duplicate-mediasession
Fix duplicate media session on linux
2022-01-16 17:29:58 +01:00
TC
40fbf3441a Backport missing changes for playlist badge 2022-01-16 17:29:21 +01:00
96f0d30818 Merge pull request #550 from Araxeus/download-playlist-badge-count
show a badge remaining items when downloading a playlist
2022-01-16 17:26:14 +01:00
90d6f13b56 Merge branch 'master' into download-playlist-badge-count 2022-01-16 17:23:43 +01:00
a0f2233db4 Merge pull request #549 from Araxeus/fix-download-button-on-playlist-menu
allow downloading playlists from popup menu
2022-01-16 17:19:51 +01:00
57ace9d504 lint 2022-01-14 00:08:43 +02:00
c2cc3cf7a0 fix duplicate mpris session 2022-01-13 14:46:41 +02:00
17a24cbb04 set badge on downloader playlist 2022-01-12 14:29:56 +02:00
74f61a532d downloader lint&refactor 2022-01-12 09:49:27 +02:00
c7e793b66e fix playback speed slider showing up where it shouldn't 2022-01-11 23:41:17 +02:00
21c149efc7 update ytdl-core 2022-01-11 22:54:53 +02:00
5e68d2487f allow downloading playlists from popup menu 2022-01-11 22:54:53 +02:00
e8bbc5ec1c fix app starting offscreen 2022-01-11 20:14:33 +02:00
97013d8373 fix: upgrade custom-electron-titlebar from 3.2.9 to 3.2.10
Snyk has created this PR to upgrade custom-electron-titlebar from 3.2.9 to 3.2.10.

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-01-08 19:06:52 +00:00
ec4c2e92af Merge pull request #539 from markbaas/mpris-artist-fix
xesam:artist should be a list
2022-01-07 22:11:54 +01:00
TC
81dadeddb9 Disable NODE_OPTIONS in entry file 2022-01-07 22:09:26 +01:00
7f3a554bc3 Merge pull request #537 from Araxeus/fix-song-info-bugs
fix notifications showing thumbnail of last song
2022-01-07 22:04:47 +01:00
98f990fcdd dont send callback on playPause() if still handling data from new song
fix notifications showing thumbnail of last song
2022-01-04 19:40:06 +02:00
75999e9dcf xesam:artist should be a list 2022-01-04 15:18:19 +01:00
4d595f56d5 Update readme (Spectron -> Playwright) 2022-01-02 22:31:19 +01:00
TC
f44b6f0c33 Bump version to 1.15.0 2021-12-30 19:39:20 +01:00
TC
c45e4e50fc Bump electron to 16.0.5 2021-12-30 19:39:03 +01:00
TC
9839a973f7 nit: re-format package.json 2021-12-30 19:35:53 +01:00
9ea967f03b Merge pull request #531 from th-ch/fix-tests
Switch from spectron to playwright to fix tests
2021-12-30 19:35:00 +01:00
TC
9d6765125b Switch from spectron to playwright to fix tests 2021-12-30 19:26:01 +01:00
TC
8d66735585 Add presets to FFmpeg in menu 2021-12-30 18:46:43 +01:00
14b4c55ce7 Merge pull request #529 from th-ch/snyk-upgrade-27f67e987dd094f8f1db19ad7f90c292
[Snyk] Upgrade @cliqz/adblocker-electron from 1.23.0 to 1.23.1
2021-12-30 17:59:53 +01:00
1d1f4bbcc3 Merge branch 'master' into snyk-upgrade-27f67e987dd094f8f1db19ad7f90c292 2021-12-30 17:59:34 +01:00
bd520c7eff Merge pull request #525 from Araxeus/fix-precise-volume-options-sync
fix precise-volume options sync
2021-12-30 17:57:13 +01:00
73e201bb2c Merge pull request #524 from MulverineX/patch-1
Add album art/thumbnail to discord activity
2021-12-30 15:10:07 +01:00
81b08917ae Merge pull request #521 from Araxeus/fix-skip-silences
fix skip-silences plugin
2021-12-30 15:08:56 +01:00
81c2ab34d9 Merge pull request #520 from th-ch/snyk-upgrade-7535be87da222abdba60d9fa36da34b5
[Snyk] Upgrade electron-updater from 4.6.2 to 4.6.3
2021-12-30 15:06:06 +01:00
TC
33faa2deb3 nit: improve comment for shared Array Buffer 2021-12-30 14:59:11 +01:00
TC
4d4ac56486 Ensure NODE_OPTIONS are unset in dev mode to avoid warning 2021-12-30 14:58:21 +01:00
56ac2b3b06 Merge pull request #515 from Araxeus/fix-useragents
update electron & remote & user agents
2021-12-30 14:57:36 +01:00
c72ea4bad5 fix: upgrade @cliqz/adblocker-electron from 1.23.0 to 1.23.1
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.23.0 to 1.23.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
2021-12-27 20:52:46 +00:00
d60069555e Merge pull request #513 from markbaas/master
fixes mpris bug in snap
2021-12-27 16:12:40 +01:00
ed7025b4a2 fix precise-volume options sync 2021-12-24 02:13:21 +02:00
5fbc0f8122 Add album art/thumbnail to discord activity 2021-12-23 11:36:11 -07:00
02a989ca07 fix unnecessary skips 2021-12-18 21:03:32 +02:00
7c6fe6748e fix: upgrade electron-updater from 4.6.2 to 4.6.3
Snyk has created this PR to upgrade electron-updater from 4.6.2 to 4.6.3.

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
2021-12-18 11:02:24 +00:00
8f2ed3039a add comment to useragent fix 2021-12-16 19:29:09 +02:00
baeebd1959 downloader fixes
* --experimental-wasm-bulk-memory
* SharedArrayBuffer
* getFolder from front
* ytdl-core 4.9.2
2021-12-16 19:15:55 +02:00
49edbf723f fix custom-electron-prompt & remote 2021-12-16 18:28:49 +02:00
3764ce9a7c update custom-electron-titlebar 2021-12-16 18:01:11 +02:00
46943520bd Merge branch 'master' into fix-useragents 2021-12-15 19:32:14 +02:00
1048b3f99a Merge pull request #519 from th-ch/skip-silences-plugin
Add "Skip silences" plugin
2021-12-14 23:31:14 +01:00
TC
b1e40271e6 nit: re-order dependencies 2021-12-14 23:17:06 +01:00
TC
11429978c9 Add plugin to skip silences 2021-12-14 23:16:41 +01:00
47ca6e0b1f use session.webRequest.onBeforeSendHeaders for useragent 2021-12-14 19:22:02 +02:00
a273f6f73c fix video toggle button appearing when in song mode 2021-12-14 00:34:32 +02:00
c8ba85be76 use firefox as falllback useragent 2021-12-14 00:34:10 +02:00
6633243628 use @rozzzly/custom-electron-titlebar 2021-12-13 21:10:59 +02:00
2fb47933ac fix useragents 2021-12-13 21:06:52 +02:00
4fd683ed23 update electron & remote module 2021-12-13 20:10:36 +02:00
e1e9748002 always on useragent 2021-12-13 01:11:13 +02:00
dd122666c5 update useragents 2021-12-13 00:56:13 +02:00
5483f0ee36 Merge pull request #510 from MiepHD/master
Aligned lyric design
2021-12-10 21:15:36 +01:00
2c6c80d829 Merge pull request #509 from Araxeus/mpris-urgent-fix
Fix mpris bugs - follows #480
2021-12-10 21:14:31 +01:00
584d3e83c6 fixes mpris bug in snap 2021-12-10 20:52:16 +01:00
58d5256dd2 1vw would fit perfectly 2021-12-05 17:41:59 +01:00
920d61a1c6 Aligned lyric design
I aligned the lyric to the normal lyrics so that the lyric isn't that tiny
2021-12-04 20:53:19 +01:00
c5c2d5b74c Hide cast button which doesn't work 2021-12-03 16:56:51 +02:00
2daee01ff7 Fix bugs from bad merge conflict solving
-fix missing songControls
-use player.seeked directly
-fix 'seeked' event listener
-fix e.target instead of e.detail in apiLoaded event
-fix document.querySelector('video') before apiLoaded
-setup timeChange Listener if linux+shortcuts enabled
2021-12-03 15:55:40 +02:00
d13c9b7ca6 Merge pull request #476 from Araxeus/mix-fixes
Various small fixes (discord, video-toggle, precise-volume, playback-speed, shortcuts, lyrics)
2021-12-02 21:11:28 +01:00
5296a88525 Merge pull request #480 from Araxeus/mpris+tuna-fix
Mpris + obs-tuna fixes
2021-12-02 21:03:03 +01:00
8ce4b5b297 lint 2021-12-01 21:44:48 +02:00
2c39c0efed Merge branch 'master' into mpris+tuna-fix 2021-12-01 20:11:59 +02:00
e917abaec9 fix merge error 2021-12-01 00:08:55 +02:00
bdd0a2e8db Merge branch 'master' into mix-fixes 2021-12-01 00:06:15 +02:00
362003e10e Merge pull request #498 from th-ch/snyk-upgrade-9d2eea8c019b6593f1ef01f7fe8f404b
[Snyk] Upgrade node-fetch from 2.6.5 to 2.6.6
2021-11-30 00:16:34 +01:00
4e4b557413 Merge pull request #491 from Araxeus/fix-blur
fix interaction between blur navbar & in-app-menu
2021-11-30 00:14:49 +01:00
3a068af925 Merge pull request #475 from th-ch/snyk-upgrade-55c8a0f6d6911f431ebf75ad846e8f6c
[Snyk] Upgrade @cliqz/adblocker-electron from 1.22.7 to 1.23.0
2021-11-30 00:12:21 +01:00
44ca812330 Merge pull request #488 from Rubecks/exponential-volume-plugin
New Plugin: Exponential Volume
2021-11-30 00:10:48 +01:00
74a69e1c7a Merge pull request #474 from th-ch/snyk-upgrade-267eeda31c348d529d38d5a6413ef858
[Snyk] Upgrade electron-updater from 4.6.0 to 4.6.1
2021-11-30 00:06:42 +01:00
c3ef16c3dd Merge pull request #477 from Araxeus/fix-loadeddata/metdata-events-rarely-not-firing
Fix loadeddata/metadata video events rarely not firing (+other small fixes)
2021-11-29 23:52:23 +01:00
8c5ac17cdf fix multiple songInfo calls on start 2021-11-23 18:53:04 +02:00
c99b95e611 use config.plugins.isEnabled 2021-11-22 22:52:38 +02:00
4362101c0a lint 2021-11-22 22:08:24 +02:00
7ba205cc6c fix backquotes in keybind prompt 2021-11-22 18:59:29 +02:00
abc1712cf7 fix counter prompt 2021-11-22 18:33:32 +02:00
92452f804f fix song-info-request 2021-11-22 18:20:12 +02:00
c76df84ce3 fix: upgrade node-fetch from 2.6.5 to 2.6.6
Snyk has created this PR to upgrade node-fetch from 2.6.5 to 2.6.6.

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
2021-11-22 04:09:33 +00:00
185ebbf417 fix downloader playlist download 2021-11-21 19:44:01 +02:00
6726e2600b rework songInfo pause listener 2021-11-14 23:49:19 +02:00
93e0664f95 fix height & blur of search page item header 2021-11-14 22:02:37 +02:00
bf45ed10aa fix #490 2021-11-14 21:45:34 +02:00
8da78d50c4 Exponential Volume Plugin 2021-11-12 17:58:39 -03:00
b27a959c2b fix video-toggle&precise-volume interaction 2021-11-12 18:42:58 +02:00
cfe719b6bd use native thumbnail without modifiers 2021-11-12 17:46:40 +02:00
071799c435 fix some shortcuts 2021-11-12 16:39:43 +02:00
87ee7ed83d lint video-toggle 2021-11-10 22:35:49 +02:00
08fdd07969 speed up sponsorblock 2021-11-10 20:44:13 +02:00
02d5b78f55 add songInfo.album 2021-11-10 20:11:45 +02:00
5492afe5f6 add catch to fetch 2021-11-10 19:08:19 +02:00
9a7baeac23 fix tuna time update 2021-11-10 18:45:42 +02:00
ccfe7434bf fix mpris 2021-11-10 18:23:55 +02:00
6dbed73e6b fix disable autoplay 2021-11-09 18:52:03 +02:00
895136af0a used youtube's videodatachange event 2021-11-09 17:57:06 +02:00
72b4398024 lint&fix video-toggle plugin 2021-11-09 15:17:26 +02:00
65ce62adc1 use $('video') srcChanged event instead of loadeddata/metadata 2021-11-09 13:29:41 +02:00
eafdd5046d fix lyric text size 2021-11-09 10:42:32 +02:00
bbece751c0 lint playback speed 2021-11-09 10:03:06 +02:00
719c244e32 fix #472 2021-11-09 10:01:33 +02:00
e70b41b256 fix: upgrade @cliqz/adblocker-electron from 1.22.7 to 1.23.0
Snyk has created this PR to upgrade @cliqz/adblocker-electron from 1.22.7 to 1.23.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
2021-11-09 05:45:20 +00:00
f4b6fd53f3 fix: upgrade electron-updater from 4.6.0 to 4.6.1
Snyk has created this PR to upgrade electron-updater from 4.6.0 to 4.6.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
2021-11-09 05:45:17 +00:00
73 changed files with 2800 additions and 1537 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 }}
@ -35,6 +35,60 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: yarn --frozen-lockfile run: yarn --frozen-lockfile
######################
# Patch SnoreToast to fix App ID - see https://github.com/th-ch/youtube-music/issues/479#issuecomment-965473559
- name: SnoreToast - parameters
id: snoretoast-params
if: startsWith(matrix.os, 'windows')
shell: bash
run: |
echo "::set-output name=version::v0.8.0"
echo "::set-output name=path::./vendor/snoretoast"
- name: SnoreToast - cache
id: snoretoast-cache
uses: actions/cache@v2
if: startsWith(matrix.os, 'windows')
with:
path: ${{ steps.snoretoast-params.outputs.path }}
key: snoretoast-${{ steps.snoretoast-params.outputs.version }}
- name: SnoreToast - compile
if: |
startsWith(matrix.os, 'windows') &&
steps.snoretoast-cache.outputs.cache-hit != 'true'
shell: bash
run: |
SNORETOAST_TAG="${{ steps.snoretoast-params.outputs.version }}"
echo "Compiling SnoreToast $SNORETOAST_TAG"
git config --global user.email "th-ch@users.noreply.github.com"
git config --global user.name "YouTube Music"
git clone -c advice.detachedHead=false --branch $SNORETOAST_TAG --depth 1 https://github.com/KDE/snoretoast.git ${{ steps.snoretoast-params.outputs.path }}
cd ${{ steps.snoretoast-params.outputs.path }}
# Apply https://github.com/KDE/snoretoast/pull/15/commits/c5faeceaf36f4b9fb27e5269990b716a25ecbe43
# Patch generated with `git format-patch -1 c5faeceaf36f4b9fb27e5269990b716a25ecbe43`
git am < ../snoretoast-patch/0001-Fix-activation-not-writing-to-pipe.patch
# Compile for win32
cmake -A Win32 -B build32
cmake --build build32 --config Release
# Compile for x64
cmake -A x64 -B build64
cmake --build build64 --config Release
- name: SnoreToast - overwrite with custom build
if: startsWith(matrix.os, 'windows')
shell: bash
run: |
# Override SnoreToast with the patched versions
cp ${{ steps.snoretoast-params.outputs.path }}/build32/bin/Release/snoretoast.exe ./node_modules/node-notifier/vendor/snoreToast/snoretoast-x86.exe
cp ${{ steps.snoretoast-params.outputs.path }}/build64/bin/Release/snoretoast.exe ./node_modules/node-notifier/vendor/snoreToast/snoretoast-x64.exe
# End of SnoreToast patch
######################
- name: Test - name: Test
uses: GabrielBB/xvfb-action@v1 uses: GabrielBB/xvfb-action@v1
with: with:

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ node_modules
/dist /dist
/assets/generated /assets/generated
electron-builder.yml electron-builder.yml
.vscode/settings.json

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

546
changelog.md Normal file
View File

@ -0,0 +1,546 @@
### Changelog
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [v1.17.0](https://github.com/th-ch/youtube-music/compare/v1.16.0...v1.17.0)
- 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)
> 20 February 2022
- 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)
- Add snoretoast custom compile script [`#600`](https://github.com/th-ch/youtube-music/pull/600)
- fix interactive notifications icon + exclude platform specific plugins from build [`#591`](https://github.com/th-ch/youtube-music/pull/591)
- Add album title to largeImage and change paused icon [`#587`](https://github.com/th-ch/youtube-music/pull/587)
- make useragent override optional [`#595`](https://github.com/th-ch/youtube-music/pull/595)
- get album name from DOM [`#588`](https://github.com/th-ch/youtube-music/pull/588)
- fix various lyrics issues [`#584`](https://github.com/th-ch/youtube-music/pull/584)
- discord set inactivity timeout prompt [`#580`](https://github.com/th-ch/youtube-music/pull/580)
- add single instance lock option [`#578`](https://github.com/th-ch/youtube-music/pull/578)
- fix "restart app on config change" option [`#561`](https://github.com/th-ch/youtube-music/pull/561)
- fix window position save spam [`#562`](https://github.com/th-ch/youtube-music/pull/562)
- load adblocker sooner [`#583`](https://github.com/th-ch/youtube-music/pull/583)
- add description of new plugins to readme [`#585`](https://github.com/th-ch/youtube-music/pull/585)
- Use `center` alignment for lyrics text [`#573`](https://github.com/th-ch/youtube-music/pull/573)
- fix precise-volume hud positioning [`#567`](https://github.com/th-ch/youtube-music/pull/567)
- update electron and dependencies [`#565`](https://github.com/th-ch/youtube-music/pull/565)
- filenamify playlist folder name [`#557`](https://github.com/th-ch/youtube-music/pull/557)
- [Snyk] Security upgrade node-fetch from 2.6.6 to 2.6.7 (3.1.1 incompatible) [`#554`](https://github.com/th-ch/youtube-music/pull/554)
- fix app starting offscreen [`#548`](https://github.com/th-ch/youtube-music/pull/548)
- Release Mac arm64 [`#566`](https://github.com/th-ch/youtube-music/pull/566)
- Build command for Apple (m1) silicon macs [`#553`](https://github.com/th-ch/youtube-music/pull/553)
- [Snyk] Upgrade custom-electron-titlebar from 3.2.9 to 3.2.10 [`#545`](https://github.com/th-ch/youtube-music/pull/545)
- Fix duplicate media session on linux [`#551`](https://github.com/th-ch/youtube-music/pull/551)
- show a badge remaining items when downloading a playlist [`#550`](https://github.com/th-ch/youtube-music/pull/550)
- allow downloading playlists from popup menu [`#549`](https://github.com/th-ch/youtube-music/pull/549)
- 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 https://github.com/th-ch/youtube-music/pull/578#issuecomment-1035517531 [`#578`](https://github.com/th-ch/youtube-music/pull/578)
- 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 dependencies [`8be07bc`](https://github.com/th-ch/youtube-music/commit/8be07bcb7ad8b727d97c36aa0760aed4e2fc481f)
#### [v1.15.0](https://github.com/th-ch/youtube-music/compare/v1.14.0...v1.15.0)
> 30 December 2021
- Switch from spectron to playwright to fix tests [`#531`](https://github.com/th-ch/youtube-music/pull/531)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.23.0 to 1.23.1 [`#529`](https://github.com/th-ch/youtube-music/pull/529)
- fix precise-volume options sync [`#525`](https://github.com/th-ch/youtube-music/pull/525)
- Add album art/thumbnail to discord activity [`#524`](https://github.com/th-ch/youtube-music/pull/524)
- fix skip-silences plugin [`#521`](https://github.com/th-ch/youtube-music/pull/521)
- [Snyk] Upgrade electron-updater from 4.6.2 to 4.6.3 [`#520`](https://github.com/th-ch/youtube-music/pull/520)
- update electron & remote & user agents [`#515`](https://github.com/th-ch/youtube-music/pull/515)
- fixes mpris bug in snap [`#513`](https://github.com/th-ch/youtube-music/pull/513)
- Add "Skip silences" plugin [`#519`](https://github.com/th-ch/youtube-music/pull/519)
- Aligned lyric design [`#510`](https://github.com/th-ch/youtube-music/pull/510)
- Fix mpris bugs - follows #480 [`#509`](https://github.com/th-ch/youtube-music/pull/509)
- Various small fixes (discord, video-toggle, precise-volume, playback-speed, shortcuts, lyrics) [`#476`](https://github.com/th-ch/youtube-music/pull/476)
- Mpris + obs-tuna fixes [`#480`](https://github.com/th-ch/youtube-music/pull/480)
- [Snyk] Upgrade node-fetch from 2.6.5 to 2.6.6 [`#498`](https://github.com/th-ch/youtube-music/pull/498)
- fix interaction between blur navbar & in-app-menu [`#491`](https://github.com/th-ch/youtube-music/pull/491)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.22.7 to 1.23.0 [`#475`](https://github.com/th-ch/youtube-music/pull/475)
- New Plugin: Exponential Volume [`#488`](https://github.com/th-ch/youtube-music/pull/488)
- [Snyk] Upgrade electron-updater from 4.6.0 to 4.6.1 [`#474`](https://github.com/th-ch/youtube-music/pull/474)
- Fix loadeddata/metadata video events rarely not firing (+other small fixes) [`#477`](https://github.com/th-ch/youtube-music/pull/477)
- fix #490 [`#490`](https://github.com/th-ch/youtube-music/issues/490)
- fix #472 [`#472`](https://github.com/th-ch/youtube-music/issues/472)
- fix mpris [`ccfe743`](https://github.com/th-ch/youtube-music/commit/ccfe7434bf708ee58156c2952234a049706edfc2)
- lint [`4362101`](https://github.com/th-ch/youtube-music/commit/4362101c0a2ebb7f0536f615cecba8a55ac96702)
- rework songInfo pause listener [`6726e26`](https://github.com/th-ch/youtube-music/commit/6726e2600b3ca3a8d68e3e1b95b50da211fa354d)
#### [v1.14.0](https://github.com/th-ch/youtube-music/compare/v1.13.0...v1.14.0)
> 7 November 2021
- [Snyk] Upgrade custom-electron-prompt from 1.1.0 to 1.2.0 [`#467`](https://github.com/th-ch/youtube-music/pull/467)
- Video Toggle Plugin [`#448`](https://github.com/th-ch/youtube-music/pull/448)
- fix playback speed plugin [`#462`](https://github.com/th-ch/youtube-music/pull/462)
- Fix sponsorblock skipping when not needed [`#465`](https://github.com/th-ch/youtube-music/pull/465)
- Sponsorblock fix + use new apiLoaded event [`#463`](https://github.com/th-ch/youtube-music/pull/463)
- use apiLoaded event in audio-compressor plugin [`#458`](https://github.com/th-ch/youtube-music/pull/458)
- alert on initial hide-menu enabled [`#456`](https://github.com/th-ch/youtube-music/pull/456)
- Blur plugin tweaks and integration with in-app-menu [`#451`](https://github.com/th-ch/youtube-music/pull/451)
- set resume on start url to songInfo.url [`#449`](https://github.com/th-ch/youtube-music/pull/449)
- quality-changer-plugin [`#446`](https://github.com/th-ch/youtube-music/pull/446)
- get songInfo from youtube API [`#443`](https://github.com/th-ch/youtube-music/pull/443)
- New plugin: Blur navigation bar [`#442`](https://github.com/th-ch/youtube-music/pull/442)
- Discord plugin: Clean Up Export (follow-up #380) [`#440`](https://github.com/th-ch/youtube-music/pull/440)
- remove upgrade button + makes images unselectable [`#434`](https://github.com/th-ch/youtube-music/pull/434)
- new auto confirm when paused [`#433`](https://github.com/th-ch/youtube-music/pull/433)
- fix: mpris instance not registering itself and media controls [`#431`](https://github.com/th-ch/youtube-music/pull/431)
- Audio compressor plugin [`#288`](https://github.com/th-ch/youtube-music/pull/288)
- precise-volume plugin fixes & updates [`#275`](https://github.com/th-ch/youtube-music/pull/275)
- Custom Prompt for changing options [`#243`](https://github.com/th-ch/youtube-music/pull/243)
- [Snyk] Upgrade async-mutex from 0.3.1 to 0.3.2 [`#412`](https://github.com/th-ch/youtube-music/pull/412)
- build(deps): bump tmpl from 1.0.4 to 1.0.5 [`#414`](https://github.com/th-ch/youtube-music/pull/414)
- [Snyk] Upgrade node-fetch from 2.6.1 to 2.6.2 [`#416`](https://github.com/th-ch/youtube-music/pull/416)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.22.5 to 1.22.6 [`#429`](https://github.com/th-ch/youtube-music/pull/429)
- build(deps-dev): bump electron from 12.0.8 to 12.1.0 [`#430`](https://github.com/th-ch/youtube-music/pull/430)
- Fix discord clearActivity, menu, listen along option [`#380`](https://github.com/th-ch/youtube-music/pull/380)
- Bump dev deps [`41a01ba`](https://github.com/th-ch/youtube-music/commit/41a01ba58a17056ba5143fdbd10d3bae11dd8d52)
- Discord add reconnecting functionality [`b5fd6b4`](https://github.com/th-ch/youtube-music/commit/b5fd6b4969a318b3738583e7f33eb2c0cf295237)
- add custom-electron-prompt [`e4eed2e`](https://github.com/th-ch/youtube-music/commit/e4eed2e51979378e62dab902e425218cae5108dc)
#### [v1.13.0](https://github.com/th-ch/youtube-music/compare/v1.12.2...v1.13.0)
> 19 September 2021
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.22.4 to 1.22.5 [`#406`](https://github.com/th-ch/youtube-music/pull/406)
- Fix incorrect Google alert caused by changing user agent coresponding to current platform [`#384`](https://github.com/th-ch/youtube-music/pull/384)
- [Snyk] Upgrade electron-updater from 4.4.3 to 4.4.6 [`#401`](https://github.com/th-ch/youtube-music/pull/401)
- [Snyk] Upgrade electron-updater from 4.4.0 to 4.4.1 [`#370`](https://github.com/th-ch/youtube-music/pull/370)
- Bump path-parse from 1.0.6 to 1.0.7 [`#375`](https://github.com/th-ch/youtube-music/pull/375)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.22.2 to 1.22.3 [`#385`](https://github.com/th-ch/youtube-music/pull/385)
- Bump jszip from 3.5.0 to 3.7.1 [`#388`](https://github.com/th-ch/youtube-music/pull/388)
- List missing plugins [`#382`](https://github.com/th-ch/youtube-music/pull/382)
- add tuna plugin for obs [`#397`](https://github.com/th-ch/youtube-music/pull/397)
- Update menu buttons to new format [`#389`](https://github.com/th-ch/youtube-music/pull/389)
- Plugin to fetch lyrics from Genius [`#387`](https://github.com/th-ch/youtube-music/pull/387)
- Add mpris support with cherry picked commit from previous PR https://github.com/th-ch/youtube-music/pull/394 [`#395`](https://github.com/th-ch/youtube-music/pull/395)
- Add "Listen Along" button, solve #353 [`#383`](https://github.com/th-ch/youtube-music/pull/383)
- Bump node to v14 [`#386`](https://github.com/th-ch/youtube-music/pull/386)
- [Snyk] Upgrade electron-updater from 4.3.9 to 4.3.10 [`#350`](https://github.com/th-ch/youtube-music/pull/350)
- [Snyk] Upgrade chokidar from 3.5.1 to 3.5.2 [`#354`](https://github.com/th-ch/youtube-music/pull/354)
- Bump ytdl/ytpl [`c01506d`](https://github.com/th-ch/youtube-music/commit/c01506dc441bfc538471dc2c552c1a8a2800c611)
- Add mpris support [`e255777`](https://github.com/th-ch/youtube-music/commit/e255777283c7b16611404cbfe260bfcca75a1e40)
- Add Genius lyrics plugin [`acbe0ac`](https://github.com/th-ch/youtube-music/commit/acbe0ac25d568c25fedb514e0e96c66497b0f2d6)
#### [v1.12.2](https://github.com/th-ch/youtube-music/compare/v1.12.1...v1.12.2)
> 1 July 2021
- Fix downloader plugin [`#339`](https://github.com/th-ch/youtube-music/pull/339)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.22.0 to 1.22.1 [`#337`](https://github.com/th-ch/youtube-music/pull/337)
- Update and simplify in-app-menu [`#249`](https://github.com/th-ch/youtube-music/pull/249)
- Bump hosted-git-info from 2.8.8 to 2.8.9 [`#331`](https://github.com/th-ch/youtube-music/pull/331)
- Bump lodash from 4.17.20 to 4.17.21 [`#330`](https://github.com/th-ch/youtube-music/pull/330)
- [Snyk] Upgrade ytdl-core from 4.8.0 to 4.8.2 [`#328`](https://github.com/th-ch/youtube-music/pull/328)
- [Snyk] Upgrade electron-updater from 4.3.8 to 4.3.9 [`#324`](https://github.com/th-ch/youtube-music/pull/324)
- Bump normalize-url from 4.5.0 to 4.5.1 [`#323`](https://github.com/th-ch/youtube-music/pull/323)
- Bump trim-newlines from 3.0.0 to 3.0.1 [`#320`](https://github.com/th-ch/youtube-music/pull/320)
- [Snyk] Upgrade @ffmpeg/core from 0.9.0 to 0.10.0 [`#317`](https://github.com/th-ch/youtube-music/pull/317)
- [Snyk] Upgrade @ffmpeg/ffmpeg from 0.9.8 to 0.10.0 [`#316`](https://github.com/th-ch/youtube-music/pull/316)
- [Snyk] Upgrade custom-electron-titlebar from 3.2.6 to 3.2.7 [`#311`](https://github.com/th-ch/youtube-music/pull/311)
- fix hidden webp thumbnail throwing MIME type error in downloader [`#318`](https://github.com/th-ch/youtube-music/pull/318)
- Add Sponsorblock plugin [`#308`](https://github.com/th-ch/youtube-music/pull/308)
- [Snyk] Upgrade @ffmpeg/ffmpeg from 0.9.7 to 0.9.8 [`#305`](https://github.com/th-ch/youtube-music/pull/305)
- Bump dependencies to fix vulnerabilities [`496836b`](https://github.com/th-ch/youtube-music/commit/496836b33b116e06b8d1361ce1f47ab6c9138cae)
- update refreshMenu() function [`33855f1`](https://github.com/th-ch/youtube-music/commit/33855f17dd80c099117a3d84bbd9b5021776771c)
- Add SponsorBlock plugin [`ca64a77`](https://github.com/th-ch/youtube-music/commit/ca64a77ed0236fd9cfb4b40e450578a186638dc7)
#### [v1.12.1](https://github.com/th-ch/youtube-music/compare/v1.12.0...v1.12.1)
> 28 May 2021
- Bump ws from 7.4.3 to 7.4.6 [`#303`](https://github.com/th-ch/youtube-music/pull/303)
- Bump browserslist from 4.16.3 to 4.16.6 [`#301`](https://github.com/th-ch/youtube-music/pull/301)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.20.4 to 1.20.5 [`#300`](https://github.com/th-ch/youtube-music/pull/300)
- [Snyk] Upgrade ytdl-core from 4.5.0 to 4.7.0 [`#299`](https://github.com/th-ch/youtube-music/pull/299)
- [Snyk] Upgrade @ffmpeg/core from 0.8.5 to 0.9.0 [`#298`](https://github.com/th-ch/youtube-music/pull/298)
- [Snyk] Upgrade filenamify from 4.2.0 to 4.3.0 [`#293`](https://github.com/th-ch/youtube-music/pull/293)
- [Snyk] Upgrade ytpl from 2.1.1 to 2.2.0 [`#285`](https://github.com/th-ch/youtube-music/pull/285)
- fix song-info callback duplication [`#269`](https://github.com/th-ch/youtube-music/pull/269)
- fix notification showing appID instead of app name on windows [`#270`](https://github.com/th-ch/youtube-music/pull/270)
- Upgrade electron to v12 [`#273`](https://github.com/th-ch/youtube-music/pull/273)
- fix last-fm overwrite config on each start [`#267`](https://github.com/th-ch/youtube-music/pull/267)
- Downloader tweaks + taskbar progress bar [`#265`](https://github.com/th-ch/youtube-music/pull/265)
- remove `open` dependency from last-fm plugin [`#262`](https://github.com/th-ch/youtube-music/pull/262)
- Fix downloader metadata if not currently playing [`#252`](https://github.com/th-ch/youtube-music/pull/252)
- fix playPause bugs by directly playPause video element [`#259`](https://github.com/th-ch/youtube-music/pull/259)
- Bump ua-parser-js from 0.7.23 to 0.7.28 [`#260`](https://github.com/th-ch/youtube-music/pull/260)
- Fix precise volume listener override [`#253`](https://github.com/th-ch/youtube-music/pull/253)
- fix css not inserting on reload [`#255`](https://github.com/th-ch/youtube-music/pull/255)
- playlist download progressBar using `chokidar` [`53bf7c5`](https://github.com/th-ch/youtube-music/commit/53bf7c5068fdc14f5aa469d47b3174d27f40e05c)
- download progress bar on taskbar [`a8ac2c3`](https://github.com/th-ch/youtube-music/commit/a8ac2c3af988f299be85010e7fea541096b7e261)
- fix: upgrade @cliqz/adblocker-electron from 1.20.4 to 1.20.5 [`c5f84b5`](https://github.com/th-ch/youtube-music/commit/c5f84b568b0c3480af1abc8ff111771e2170a50e)
#### [v1.12.0](https://github.com/th-ch/youtube-music/compare/v1.11.0...v1.12.0)
> 4 May 2021
- Menu tweaks [`#224`](https://github.com/th-ch/youtube-music/pull/224)
- Interactive notifications for windows [`#228`](https://github.com/th-ch/youtube-music/pull/228)
- [Plugin] Precise volume control [`#236`](https://github.com/th-ch/youtube-music/pull/236)
- [Snyk] Upgrade electron-store from 7.0.2 to 7.0.3 [`#244`](https://github.com/th-ch/youtube-music/pull/244)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.20.3 to 1.20.4 [`#233`](https://github.com/th-ch/youtube-music/pull/233)
- Dependencies update [`#231`](https://github.com/th-ch/youtube-music/pull/231)
- Fix downloader metadata [`#245`](https://github.com/th-ch/youtube-music/pull/245)
- Last.fm support [`#196`](https://github.com/th-ch/youtube-music/pull/196)
- simple fix for discord plugin [`#239`](https://github.com/th-ch/youtube-music/pull/239)
- In-app-menu plugin - rename plugin & configure menu builder [`#215`](https://github.com/th-ch/youtube-music/pull/215)
- Allows downloading songs that aren't currently playing [`#221`](https://github.com/th-ch/youtube-music/pull/221)
- Updated download plugin icon color to match other icons [`#222`](https://github.com/th-ch/youtube-music/pull/222)
- [Notification Plugin] Fix duplicate notification [`#216`](https://github.com/th-ch/youtube-music/pull/216)
- Pass metadata to front + use metadata URL in downloader [`#213`](https://github.com/th-ch/youtube-music/pull/213)
- Refresh menu on plugin enable/disable (show/hide submenu) [`#217`](https://github.com/th-ch/youtube-music/pull/217)
- remove 'shortcuts' from default plugins [`#218`](https://github.com/th-ch/youtube-music/pull/218)
- [Plugin] styled-bars [`#201`](https://github.com/th-ch/youtube-music/pull/201)
- Add configurable notification urgency [`#212`](https://github.com/th-ch/youtube-music/pull/212)
- add Download Folder Chooser [`#207`](https://github.com/th-ch/youtube-music/pull/207)
- Improved songinfo provider, by using the data from the '/player' request [`#194`](https://github.com/th-ch/youtube-music/pull/194)
- Download plugin directory chooser [`#10`](https://github.com/th-ch/youtube-music/pull/10)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.20.0 to 1.20.1 [`#180`](https://github.com/th-ch/youtube-music/pull/180)
- [Plugin] taskbar-mediacontrol (for Windows) [`#200`](https://github.com/th-ch/youtube-music/pull/200)
- merge source [`#3`](https://github.com/th-ch/youtube-music/pull/3)
- merge source [`#2`](https://github.com/th-ch/youtube-music/pull/2)
- Add playlist feature in downloader plugin + custom menus in plugin system [`#203`](https://github.com/th-ch/youtube-music/pull/203)
- Added Discord timeout [`#192`](https://github.com/th-ch/youtube-music/pull/192)
- Override hide(),show(),isVisible from inside plugin [`6427b34`](https://github.com/th-ch/youtube-music/commit/6427b3406c8d84c5b7ecbe6a28158d5dc895c3c2)
- added back original yarn.lock [`24fea5a`](https://github.com/th-ch/youtube-music/commit/24fea5a24afd4f547628549962d24756cca5e413)
- remove local prompt [`8dc486f`](https://github.com/th-ch/youtube-music/commit/8dc486f18fe02a218b149838dc7ab939ec1b698a)
#### [v1.11.0](https://github.com/th-ch/youtube-music/compare/v1.10.0...v1.11.0)
> 9 March 2021
- [Snyk] Upgrade electron-store from 7.0.1 to 7.0.2 [`#178`](https://github.com/th-ch/youtube-music/pull/178)
- Added function to toggle resuming of last song when app starts [`#177`](https://github.com/th-ch/youtube-music/pull/177)
- [Snyk] Upgrade discord-rpc from 3.1.4 to 3.2.0 [`#175`](https://github.com/th-ch/youtube-music/pull/175)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.19.0 to 1.20.0 [`#154`](https://github.com/th-ch/youtube-music/pull/154)
- Added metadata to downloader plugin, and updated packages [`dd1bdae`](https://github.com/th-ch/youtube-music/commit/dd1bdae9478ef831ee2a00b29be04c65626933f8)
- Fix download/speed menu item [`796a7aa`](https://github.com/th-ch/youtube-music/commit/796a7aaaf1ecaf80b2ef113137f2222499803e29)
- fix: upgrade @cliqz/adblocker-electron from 1.19.0 to 1.20.0 [`538ab52`](https://github.com/th-ch/youtube-music/commit/538ab52abd46c2e3c6abb529c5137b5286d29670)
#### [v1.10.0](https://github.com/th-ch/youtube-music/compare/v1.9.0...v1.10.0)
> 7 February 2021
- [Snyk] Upgrade @ffmpeg/ffmpeg from 0.9.6 to 0.9.7 [`#146`](https://github.com/th-ch/youtube-music/pull/146)
- Reuse the same notification, instead of creating a new one each time the song changes. [`#144`](https://github.com/th-ch/youtube-music/pull/144)
- [Snyk] Upgrade ytdl-core from 4.2.1 to 4.3.0 [`#136`](https://github.com/th-ch/youtube-music/pull/136)
- bring the new commits to this fork [`#1`](https://github.com/th-ch/youtube-music/pull/1)
- GH page [`3bcf409`](https://github.com/th-ch/youtube-music/commit/3bcf409f2b1629333714b187c606891cedb12512)
- Add plugin to control playback speed like in YouTube (from 0.25 to 2) [`f7f3185`](https://github.com/th-ch/youtube-music/commit/f7f31850d3d9879002dc47326e4f6ec9a52c25a1)
- Update back.js [`1fdf241`](https://github.com/th-ch/youtube-music/commit/1fdf2416ad414035104bfb51b8450d82e566cb13)
#### [v1.9.0](https://github.com/th-ch/youtube-music/compare/v1.8.2...v1.9.0)
> 15 January 2021
- [Snyk] Upgrade electron-debug from 3.1.0 to 3.2.0 [`#121`](https://github.com/th-ch/youtube-music/pull/121)
- Refactor providers [`#125`](https://github.com/th-ch/youtube-music/pull/125)
- Added Discord rich presence and added extra properties to songInfo provider [`#124`](https://github.com/th-ch/youtube-music/pull/124)
- Fix plugins with context isolation [`#127`](https://github.com/th-ch/youtube-music/pull/127)
- Windows portable exe [`#126`](https://github.com/th-ch/youtube-music/pull/126)
- Split providers in 2 [`0743034`](https://github.com/th-ch/youtube-music/commit/0743034de0443e889ec11d7ea83727ff4fb96599)
- Added Discord rich presence and added extra properties to songinfo provider [`a8ce87f`](https://github.com/th-ch/youtube-music/commit/a8ce87f2ccb4f0fdbd36676883e6a0497bebc263)
- Update discord plugin for new provider + wait for ready [`aec542e`](https://github.com/th-ch/youtube-music/commit/aec542e95e2837f54bf19de675f311444789ea4e)
#### [v1.8.2](https://github.com/th-ch/youtube-music/compare/v1.8.1...v1.8.2)
> 12 January 2021
- Downloader plugin - custom audio format [`#118`](https://github.com/th-ch/youtube-music/pull/118)
- Globalized the song info and song controls, and updated Touch Bar for it. [`#102`](https://github.com/th-ch/youtube-music/pull/102)
- Bump electron to v11 [`#120`](https://github.com/th-ch/youtube-music/pull/120)
- Globalized the songinfo and song controls, and changed the pause/play button. [`9be3e1a`](https://github.com/th-ch/youtube-music/commit/9be3e1afe91f0aa3419040bba65e7b3b83b469c6)
- Simplifies the notification plugin to use the globalized song info [`5bffdbd`](https://github.com/th-ch/youtube-music/commit/5bffdbd6285a6816749c467d6e912d14748f9959)
- Loads providers before plugins [`3a5d9bd`](https://github.com/th-ch/youtube-music/commit/3a5d9bd973bdd67e77f8a7687c1430245a9490bd)
#### [v1.8.1](https://github.com/th-ch/youtube-music/compare/v1.8.0...v1.8.1)
> 8 January 2021
- [Snyk] Upgrade electron-updater from 4.3.5 to 4.3.6 [`#116`](https://github.com/th-ch/youtube-music/pull/116)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.18.8 to 1.19.0 [`#117`](https://github.com/th-ch/youtube-music/pull/117)
- [Snyk] Upgrade ytdl-core from 4.1.1 to 4.1.2 [`#109`](https://github.com/th-ch/youtube-music/pull/109)
- Bump node-notifier from 8.0.0 to 8.0.1 [`#104`](https://github.com/th-ch/youtube-music/pull/104)
- fix: upgrade electron-updater from 4.3.5 to 4.3.6 [`0bf77e5`](https://github.com/th-ch/youtube-music/commit/0bf77e592a87eb8a5222cf2c1588488a51044422)
- fix: upgrade @cliqz/adblocker-electron from 1.18.8 to 1.19.0 [`5c0cc08`](https://github.com/th-ch/youtube-music/commit/5c0cc08d80d60c46e8b27343c6fc302f64fe89e2)
- fix: upgrade ytdl-core from 4.1.1 to 4.1.2 [`e2cc262`](https://github.com/th-ch/youtube-music/commit/e2cc2628aea653739f878ec2cd2e72e2e70018a1)
#### [v1.8.0](https://github.com/th-ch/youtube-music/compare/v1.7.5...v1.8.0)
> 20 December 2020
- Added Touch Bar plugin [`#101`](https://github.com/th-ch/youtube-music/pull/101)
- [Snyk] Upgrade @ffmpeg/core from 0.8.4 to 0.8.5 [`#99`](https://github.com/th-ch/youtube-music/pull/99)
- [Snyk] Upgrade @ffmpeg/ffmpeg from 0.9.5 to 0.9.6 [`#100`](https://github.com/th-ch/youtube-music/pull/100)
- [Readme] Web folder for readme assets + new SVG animation [`#96`](https://github.com/th-ch/youtube-music/pull/96)
- Add new Linux targets (deb, freebsd, rpm) [`#94`](https://github.com/th-ch/youtube-music/pull/94)
- Web folder for readme assets + new svg animation [`01fc965`](https://github.com/th-ch/youtube-music/commit/01fc9651705f457da63615ff774f00957f783d3d)
- touchbar plugin - fixed code style [`7473677`](https://github.com/th-ch/youtube-music/commit/7473677477071ca5e7b18bda3193e345d7fd549f)
- added initial touchbar support [`c3e2c13`](https://github.com/th-ch/youtube-music/commit/c3e2c1380810d156d9d6863fffc804242171bec0)
#### [v1.7.5](https://github.com/th-ch/youtube-music/compare/v1.7.4...v1.7.5)
> 12 December 2020
- Bump ini from 1.3.5 to 1.3.7 [`#92`](https://github.com/th-ch/youtube-music/pull/92)
- Fix adblocking [`#90`](https://github.com/th-ch/youtube-music/pull/90)
- Bump adblocker dependency [`49497d0`](https://github.com/th-ch/youtube-music/commit/49497d0efb28ee0be5b16d0f1c3660efafcd289c)
- Fix adblocker preloading to inject scripts/styles [`66c5ce4`](https://github.com/th-ch/youtube-music/commit/66c5ce46caa85a7ae4ceb3d63a9e168827015c71)
- Add uBlock Origin filters to default sources [`79c7959`](https://github.com/th-ch/youtube-music/commit/79c795927a3be96456a2f45159285c64166a29b8)
#### [v1.7.4](https://github.com/th-ch/youtube-music/compare/v1.7.3...v1.7.4)
> 8 December 2020
#### [v1.7.3](https://github.com/th-ch/youtube-music/compare/v1.7.2...v1.7.3)
> 8 December 2020
- Adblocker: add option to disable default lists [`22c7f70`](https://github.com/th-ch/youtube-music/commit/22c7f70c938566a9db9c4d46a57224cfdee43df0)
#### [v1.7.2](https://github.com/th-ch/youtube-music/compare/v1.7.1...v1.7.2)
> 6 December 2020
- Add AUR badge + beautify badges [`#82`](https://github.com/th-ch/youtube-music/pull/82)
- Bugfix: only use cache with no additional blocklists [`467171a`](https://github.com/th-ch/youtube-music/commit/467171a17e648331d63f166c2da2f3134e95b37f)
- Add AUR tag + beautify tags [`d212206`](https://github.com/th-ch/youtube-music/commit/d21220693b9ffa26e05fe1963376b636b40b9952)
- Readme: add youtube-music logo to badges [`3022fac`](https://github.com/th-ch/youtube-music/commit/3022facbead40ccd81629c37b870ab33ce7fa106)
#### [v1.7.1](https://github.com/th-ch/youtube-music/compare/v1.7.0...v1.7.1)
> 3 December 2020
- Option to restart the app on config changes [`fd97576`](https://github.com/th-ch/youtube-music/commit/fd97576611ae80b959ffe7984e88ddc8d28a1ffc)
- Bump version to 1.7.1 [`e07cac2`](https://github.com/th-ch/youtube-music/commit/e07cac240691b1c9d6909e457824616182374c3a)
#### [v1.7.0](https://github.com/th-ch/youtube-music/compare/v1.6.5...v1.7.0)
> 3 December 2020
- Refactor config, custom plugin options [`#79`](https://github.com/th-ch/youtube-music/pull/79)
- Refactor config for simpler use and advanced options in plugins [`8ab2da0`](https://github.com/th-ch/youtube-music/commit/8ab2da0482b6211b6b6d43423ec06daed48dac4f)
- Allow editing config (advanced) [`f4fe5c2`](https://github.com/th-ch/youtube-music/commit/f4fe5c2a58e1ad555c321f27c00d2d78184fc687)
- Adblocker - advanced options (caching or not, additional lists) [`b94d0d4`](https://github.com/th-ch/youtube-music/commit/b94d0d4e8bd3a92bbb5e012a63fa782baa774be7)
#### [v1.6.5](https://github.com/th-ch/youtube-music/compare/v1.6.4...v1.6.5)
> 2 December 2020
- Add option to disable hardware acceleration [`#77`](https://github.com/th-ch/youtube-music/pull/77)
- Downloader plugin - retry and upgrade dependencies [`#76`](https://github.com/th-ch/youtube-music/pull/76)
- Reflect Arch Linux package name change [`#70`](https://github.com/th-ch/youtube-music/pull/70)
- Option to hide menu [`#67`](https://github.com/th-ch/youtube-music/pull/67)
- Add Arch Linux installation instructions [`#68`](https://github.com/th-ch/youtube-music/pull/68)
- Update ytdl-core to 4.1.1 [`33a11ef`](https://github.com/th-ch/youtube-music/commit/33a11efe9acad234e41ad9044ae9e67fd573b7f4)
- Autoupdate modal: add download/disable updates buttons [`ae5b85d`](https://github.com/th-ch/youtube-music/commit/ae5b85d8d748659f2e23d417560026f24ab8ce9c)
- Option to hide menu (win/linux) [`4bac3ac`](https://github.com/th-ch/youtube-music/commit/4bac3ace186c5be2cb9409d2b703f960bd662145)
#### [v1.6.4](https://github.com/th-ch/youtube-music/compare/v1.6.3...v1.6.4)
> 24 November 2020
#### [v1.6.3](https://github.com/th-ch/youtube-music/compare/v1.6.2...v1.6.3)
> 24 November 2020
- Improve CI [`#64`](https://github.com/th-ch/youtube-music/pull/64)
- Ensure menu is visible on all platforms [`#63`](https://github.com/th-ch/youtube-music/pull/63)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.18.3 to 1.18.4 [`#62`](https://github.com/th-ch/youtube-music/pull/62)
- fix: upgrade @cliqz/adblocker-electron from 1.18.3 to 1.18.4 [`2b243f6`](https://github.com/th-ch/youtube-music/commit/2b243f6dcb00d3b6f27fd066c093e7b16bb384e2)
- CI: cache yarn directory [`0fd4933`](https://github.com/th-ch/youtube-music/commit/0fd49330d3218ec5f1bc62b72ace28e79d02bc93)
- Run CI on every push/PR [`cf4827d`](https://github.com/th-ch/youtube-music/commit/cf4827d780fee510a27eecf42453b0505c52bcf9)
#### [v1.6.2](https://github.com/th-ch/youtube-music/compare/v1.6.0...v1.6.2)
> 22 November 2020
- Add github action to build/release [`#60`](https://github.com/th-ch/youtube-music/pull/60)
- Bump to node 12 [`#59`](https://github.com/th-ch/youtube-music/pull/59)
- Bump to node 12 [`#59`](https://github.com/th-ch/youtube-music/pull/59)
- Add downloader (video -&gt; mp3) plugin (in music menu) [`e197087`](https://github.com/th-ch/youtube-music/commit/e197087a5027af1ca71ecde7bbdf6351137555b9)
- Delete AppVeyor/Travis CI integration [`941dd90`](https://github.com/th-ch/youtube-music/commit/941dd90d77a5c46ed5505918374693fcd892af1f)
- GH action to build/release [`fc4754a`](https://github.com/th-ch/youtube-music/commit/fc4754a1709e6eb70d662f89eafd360aa4a77aa2)
#### [v1.6.0](https://github.com/th-ch/youtube-music/compare/v1.5.0...v1.6.0)
> 11 November 2020
- [Snyk] Upgrade electron-store from 6.0.0 to 6.0.1 [`#54`](https://github.com/th-ch/youtube-music/pull/54)
- Add notifications plugin (notify of song on play event) [`bcff6e5`](https://github.com/th-ch/youtube-music/commit/bcff6e51348645395549c206717225fb16a29cda)
- Plugins/event handlers in each window [`9bc81da`](https://github.com/th-ch/youtube-music/commit/9bc81da6f2c7f5f35769489e179851bdd80a7da8)
- Option to toggle devtools [`3e97e93`](https://github.com/th-ch/youtube-music/commit/3e97e9307cf0991adc5584a603c292b03bc6202d)
#### [v1.5.0](https://github.com/th-ch/youtube-music/compare/v1.4.0...v1.5.0)
> 4 October 2020
- Bump node-fetch from 2.6.0 to 2.6.1 [`#45`](https://github.com/th-ch/youtube-music/pull/45)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.17.0 to 1.18.0 [`#47`](https://github.com/th-ch/youtube-music/pull/47)
- [Snyk] Upgrade electron-updater from 4.3.3 to 4.3.4 [`#40`](https://github.com/th-ch/youtube-music/pull/40)
- Bump elliptic from 6.5.2 to 6.5.3 [`#38`](https://github.com/th-ch/youtube-music/pull/38)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.16.0 to 1.16.1 [`#37`](https://github.com/th-ch/youtube-music/pull/37)
- Bump lodash from 4.17.15 to 4.17.19 [`#34`](https://github.com/th-ch/youtube-music/pull/34)
- Option to start at login [`#32`](https://github.com/th-ch/youtube-music/pull/32)
- Bump dependencies [`97dce5a`](https://github.com/th-ch/youtube-music/commit/97dce5ad41ba7ff7a12d4e57a6a0acfeccd666d8)
- Bump electron to v10 (+ remove devtron, bump spectron) [`5f0dcbb`](https://github.com/th-ch/youtube-music/commit/5f0dcbb3fc9b2912bba690db232184d32c599150)
- Navigation plugin: fix arrow style [`8d74a0a`](https://github.com/th-ch/youtube-music/commit/8d74a0a9b52c5b5a04b0986e5fbec9b47a35823e)
#### [v1.4.0](https://github.com/th-ch/youtube-music/compare/v1.3.3...v1.4.0)
> 12 July 2020
- Bump electron from 8.2.1 to 8.2.4 [`#31`](https://github.com/th-ch/youtube-music/pull/31)
- [Snyk] Upgrade electron-store from 5.1.1 to 5.2.0 [`#30`](https://github.com/th-ch/youtube-music/pull/30)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.14.4 to 1.15.0 [`#29`](https://github.com/th-ch/youtube-music/pull/29)
- [Snyk] Upgrade electron-debug from 3.0.1 to 3.1.0 [`#28`](https://github.com/th-ch/youtube-music/pull/28)
- [Snyk] Upgrade electron-updater from 4.3.1 to 4.3.2 [`#27`](https://github.com/th-ch/youtube-music/pull/27)
- [Snyk] Upgrade electron-updater from 4.3.0 to 4.3.1 [`#26`](https://github.com/th-ch/youtube-music/pull/26)
- [Snyk] Upgrade @cliqz/adblocker-electron from 1.14.1 to 1.14.2 [`#25`](https://github.com/th-ch/youtube-music/pull/25)
- [Tests] Add integration tests [`#24`](https://github.com/th-ch/youtube-music/pull/24)
- Add jest, spectron and getPort util for tests [`736a706`](https://github.com/th-ch/youtube-music/commit/736a70680108620cdecab2da9dd48e10354c713e)
- fix: upgrade electron-updater from 4.3.1 to 4.3.2 [`8c94510`](https://github.com/th-ch/youtube-music/commit/8c945100e24187885dbbe5bb7830b1da11e4eaa2)
- Add jest config and test environment to launch app [`bce5b7d`](https://github.com/th-ch/youtube-music/commit/bce5b7d8ebd96886d462a3c999d72e6c69b6f807)
#### [v1.3.3](https://github.com/th-ch/youtube-music/compare/v1.3.2...v1.3.3)
> 29 April 2020
- Move tray click callback in setUpTray [`4824dda`](https://github.com/th-ch/youtube-music/commit/4824dda5d52565deb5cd6ef4b51d2d742677a154)
- Bump version to 1.3.3 [`37cac19`](https://github.com/th-ch/youtube-music/commit/37cac19d9ccae59b89a68b995eaf7e08c7d24d11)
#### [v1.3.2](https://github.com/th-ch/youtube-music/compare/v1.3.1...v1.3.2)
> 26 April 2020
- [Snyk] Upgrade electron-updater from 4.2.5 to 4.3.0 [`#22`](https://github.com/th-ch/youtube-music/pull/22)
- fix: upgrade electron-updater from 4.2.5 to 4.3.0 [`9821300`](https://github.com/th-ch/youtube-music/commit/98213005d09d00bf013d2217809736bdc334ede6)
- Hide the app (no quit) on close if tray enabled [`430687f`](https://github.com/th-ch/youtube-music/commit/430687f4d6d301aaeaeeaa11ae34d971ac3280df)
- Show/hide window when clicking on tray [`058371a`](https://github.com/th-ch/youtube-music/commit/058371ace8fbd3d9f126454fdc7dbff86df05506)
#### [v1.3.1](https://github.com/th-ch/youtube-music/compare/v1.2.0...v1.3.1)
> 12 April 2020
- Add options and tray [`#21`](https://github.com/th-ch/youtube-music/pull/21)
- Upgrade outdated dependencies [`#20`](https://github.com/th-ch/youtube-music/pull/20)
- [Plugins] Migrate ad blocker [`#19`](https://github.com/th-ch/youtube-music/pull/19)
- Upgrade xo [`297de08`](https://github.com/th-ch/youtube-music/commit/297de08278c2704b3baf65c455bba72f72acc06f)
- Bump electron-builder (needed after electron upgrade) [`3d9e59d`](https://github.com/th-ch/youtube-music/commit/3d9e59dc90e0e994e20af55af9134477e68907a5)
- Migrate from adblock-rs to cliqz [`422c3fc`](https://github.com/th-ch/youtube-music/commit/422c3fc28d83da309a80447dcd5064a4346580e8)
#### [v1.2.0](https://github.com/th-ch/youtube-music/compare/v1.1.6...v1.2.0)
> 15 March 2020
- [Snyk] Upgrade electron-localshortcut from 3.1.0 to 3.2.1 [`#13`](https://github.com/th-ch/youtube-music/pull/13)
- [Snyk] Upgrade electron-updater from 4.0.6 to 4.2.2 [`#12`](https://github.com/th-ch/youtube-music/pull/12)
- [Snyk] Upgrade electron-debug from 2.1.0 to 2.2.0 [`#15`](https://github.com/th-ch/youtube-music/pull/15)
- Fix vulnerability [`#16`](https://github.com/th-ch/youtube-music/pull/16)
- Plugin: autoconfirm when paused [`#11`](https://github.com/th-ch/youtube-music/pull/11)
- Migrate to yarn to install packages without package.json (but keep npm rebuild) [`9371a48`](https://github.com/th-ch/youtube-music/commit/9371a4827e2312258a4f692c18f964155d57ceb8)
- Bump electron-store to fix a vulnerability [`7050dfc`](https://github.com/th-ch/youtube-music/commit/7050dfca5c6a545dabc334690572d7f88b37e027)
- Bump electron updater [`f25bb59`](https://github.com/th-ch/youtube-music/commit/f25bb59065d84cde202b5192688847c528c6ef61)
#### [v1.1.6](https://github.com/th-ch/youtube-music/compare/v1.1.5...v1.1.6)
> 11 September 2019
- Bump eslint-utils from 1.3.1 to 1.4.2 [`#7`](https://github.com/th-ch/youtube-music/pull/7)
- Bump lodash.mergewith from 4.6.1 to 4.6.2 [`#4`](https://github.com/th-ch/youtube-music/pull/4)
- Bump lodash from 4.17.11 to 4.17.14 [`#5`](https://github.com/th-ch/youtube-music/pull/5)
- npm audit fix [`1a72129`](https://github.com/th-ch/youtube-music/commit/1a72129108935cbe732621d93b877e90d11a4195)
- Fix Google login [`746b5f1`](https://github.com/th-ch/youtube-music/commit/746b5f13bb08c614df290e69946cfd116a550521)
- Bump version to 1.1.6 [`6fd10ea`](https://github.com/th-ch/youtube-music/commit/6fd10ea4a0f63e9a46e7307d811977f4e0f3213f)
#### [v1.1.5](https://github.com/th-ch/youtube-music/compare/v1.1.4...v1.1.5)
> 6 July 2019
- Fix navigation plugin [`b10a1bb`](https://github.com/th-ch/youtube-music/commit/b10a1bb32dbea187422a43487527c379a9ddbb26)
- Bump version to 1.1.5 [`07c4a42`](https://github.com/th-ch/youtube-music/commit/07c4a429c15f22b173629618518abb97d9ec0100)
#### [v1.1.4](https://github.com/th-ch/youtube-music/compare/v1.1.3...v1.1.4)
> 8 June 2019
- isDev -&gt; is package [`a85325f`](https://github.com/th-ch/youtube-music/commit/a85325f33dbd40517b6029e500569fc1640af2ef)
- Add titlebar/frame only on MacOS [`b1c4cc9`](https://github.com/th-ch/youtube-music/commit/b1c4cc9c45cc48413118aec8ce54767b1983a3e7)
- Bump version to 1.1.4 [`0420f2e`](https://github.com/th-ch/youtube-music/commit/0420f2e49e295cede0db22dbb1f35ffafd6318ed)
#### [v1.1.3](https://github.com/th-ch/youtube-music/compare/v1.1.2...v1.1.3)
> 2 June 2019
- Bump fstream from 1.0.11 to 1.0.12 [`#3`](https://github.com/th-ch/youtube-music/pull/3)
- Version 1.1.3 + npm audit fix [`147ac48`](https://github.com/th-ch/youtube-music/commit/147ac48de6540c836e835fefe47e66e55dbdc9bc)
- Fix case for {en/dis}ablePlugin [`e86d63d`](https://github.com/th-ch/youtube-music/commit/e86d63da8cb083b89c2a26e6514a5b0df8868b13)
- Remove outdated download links [`ec58b5c`](https://github.com/th-ch/youtube-music/commit/ec58b5cbedda8d6f881f0e81f185a1707dbe5fab)
#### [v1.1.2](https://github.com/th-ch/youtube-music/compare/v1.1.1...v1.1.2)
> 1 May 2019
- Display error/retry in case of failure [`5a1d7fb`](https://github.com/th-ch/youtube-music/commit/5a1d7fbf230fcd840a3ea654f31602fb5f504852)
- Bump version to 1.1.2 [`eac2c5c`](https://github.com/th-ch/youtube-music/commit/eac2c5cf14d0a348704f7fbf0ff0bdce02758670)
#### [v1.1.1](https://github.com/th-ch/youtube-music/compare/v1.1.0...v1.1.1)
> 28 April 2019
- Update package lock [`2d3f77d`](https://github.com/th-ch/youtube-music/commit/2d3f77d96211460bb81a73c8c62b9e5407a7cf30)
- Add travis config [`5279a45`](https://github.com/th-ch/youtube-music/commit/5279a45f3537170006ba04cd5d59ac8b879d78a5)
- Add Appveyor config [`abc2bb8`](https://github.com/th-ch/youtube-music/commit/abc2bb8a4f749704f2daf376c0d392030f030caf)
#### [v1.1.0](https://github.com/th-ch/youtube-music/compare/v1.0.0...v1.1.0)
> 19 April 2019
- Build script + check for updates [`b3c24a5`](https://github.com/th-ch/youtube-music/commit/b3c24a521281c352c37d649e8334b581b2a1de4f)
- Add download section in readme [`828e8d4`](https://github.com/th-ch/youtube-music/commit/828e8d472ca3d76dea71d95a85f8fa726404b8e7)
- Add release/licence badge in readme [`9d343bf`](https://github.com/th-ch/youtube-music/commit/9d343bf779f2fa830302cc84c484bf4a93a25f36)
#### v1.0.0
> 19 April 2019
- Initial commit - app + 4 plugins [`8787b5c`](https://github.com/th-ch/youtube-music/commit/8787b5c175d02b52de65f2c559b411d999fa51e4)
- Fix screenshot shadow + compress image [`c5c128f`](https://github.com/th-ch/youtube-music/commit/c5c128fa0f77c69e9bf12f6ca551315b37c51e84)
- Missing quote in readme [`4b446ac`](https://github.com/th-ch/youtube-music/commit/4b446ac7c816c660cf369f3b8b6e420f766ee35f)

View File

@ -36,6 +36,7 @@ const defaultConfig = {
enabled: false, enabled: false,
ffmpegArgs: [], // e.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s ffmpegArgs: [], // e.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s
downloadFolder: undefined, // Custom download folder (absolute path) downloadFolder: undefined, // Custom download folder (absolute path)
preset: "mp3",
}, },
"last-fm": { "last-fm": {
enabled: false, enabled: false,
@ -79,6 +80,7 @@ const defaultConfig = {
}, },
"video-toggle": { "video-toggle": {
enabled: false, enabled: false,
mode: "custom",
forceHide: false, forceHide: false,
}, },
}, },

View File

@ -1,11 +1,17 @@
const defaultConfig = require("./defaults"); const defaultConfig = require("./defaults");
const plugins = require("./plugins"); const plugins = require("./plugins");
const store = require("./store"); const store = require("./store");
const { restart } = require("../providers/app-controls");
const set = (key, value) => { const set = (key, value) => {
store.set(key, value); store.set(key, value);
}; };
function setMenuOption(key, value) {
set(key, value);
if (store.get("options.restartOnConfigChanges")) restart();
}
const get = (key) => { const get = (key) => {
return store.get(key); return store.get(key);
}; };
@ -14,6 +20,7 @@ module.exports = {
defaultConfig, defaultConfig,
get, get,
set, set,
setMenuOption,
edit: () => store.openInEditor(), edit: () => store.openInEditor(),
watch: (cb) => { watch: (cb) => {
store.onDidChange("options", cb); store.onDidChange("options", cb);

View File

@ -1,4 +1,5 @@
const store = require("./store"); const store = require("./store");
const { restart } = require("../providers/app-controls");
function getEnabled() { function getEnabled() {
const plugins = store.get("plugins"); const plugins = store.get("plugins");
@ -24,16 +25,21 @@ function setOptions(plugin, options) {
}); });
} }
function setMenuOptions(plugin, options) {
setOptions(plugin, options);
if (store.get("options.restartOnConfigChanges")) restart();
}
function getOptions(plugin) { function getOptions(plugin) {
return store.get("plugins")[plugin]; return store.get("plugins")[plugin];
} }
function enable(plugin) { function enable(plugin) {
setOptions(plugin, { enabled: true }); setMenuOptions(plugin, { enabled: true });
} }
function disable(plugin) { function disable(plugin) {
setOptions(plugin, { enabled: false }); setMenuOptions(plugin, { enabled: false });
} }
module.exports = { module.exports = {
@ -42,5 +48,6 @@ module.exports = {
enable, enable,
disable, disable,
setOptions, setOptions,
setMenuOptions,
getOptions, getOptions,
}; };

View File

@ -3,6 +3,11 @@ const Store = require("electron-store");
const defaults = require("./defaults"); const defaults = require("./defaults");
const migrations = { const migrations = {
">=1.17.0": (store) => {
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"

149
index.js
View File

@ -2,6 +2,8 @@
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");
@ -20,12 +22,32 @@ unhandled({
showDialog: false, showDialog: false,
}); });
// Disable Node options if the env var is set
process.env.NODE_OPTIONS = "";
const app = electron.app; const app = electron.app;
// Prevent window being garbage collected
let mainWindow;
autoUpdater.autoDownload = false;
if(config.get("options.singleInstanceLock")){
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) app.quit();
app.on('second-instance', () => {
if (!mainWindow) return;
if (mainWindow.isMinimized()) mainWindow.restore();
if (!mainWindow.isVisible()) mainWindow.show();
mainWindow.focus();
});
}
app.commandLine.appendSwitch( app.commandLine.appendSwitch(
"js-flags", "js-flags",
// WebAssembly flags // WebAssembly flags
"--experimental-wasm-threads --experimental-wasm-bulk-memory" "--experimental-wasm-threads"
); );
app.commandLine.appendSwitch("enable-features", "SharedArrayBuffer"); // Required for downloader
app.allowRendererProcessReuse = true; // https://github.com/electron/electron/issues/18397 app.allowRendererProcessReuse = true; // https://github.com/electron/electron/issues/18397
if (config.get("options.disableHardwareAcceleration")) { if (config.get("options.disableHardwareAcceleration")) {
if (is.dev()) { if (is.dev()) {
@ -34,6 +56,11 @@ if (config.get("options.disableHardwareAcceleration")) {
app.disableHardwareAcceleration(); app.disableHardwareAcceleration();
} }
if (is.linux() && config.plugins.isEnabled("shortcuts")) {
//stops chromium from launching it's own mpris service
app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
}
if (config.get("options.proxy")) { if (config.get("options.proxy")) {
app.commandLine.appendSwitch("proxy-server", config.get("options.proxy")); app.commandLine.appendSwitch("proxy-server", config.get("options.proxy"));
} }
@ -43,10 +70,6 @@ require("electron-debug")({
showDevTools: false //disable automatic devTools on new window showDevTools: false //disable automatic devTools on new window
}); });
// Prevent window being garbage collected
let mainWindow;
autoUpdater.autoDownload = false;
let icon = "assets/youtube-music.png"; let icon = "assets/youtube-music.png";
if (process.platform == "win32") { if (process.platform == "win32") {
icon = "assets/generated/icon.ico"; icon = "assets/generated/icon.ico";
@ -98,7 +121,6 @@ function createMainWindow() {
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 nativeWindowOpen: true, // window.open return Window object(like in regular browsers), not BrowserWindowProxy
enableRemoteModule: true,
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()
? { ? {
@ -116,14 +138,37 @@ 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;
win.setPosition(x, y); const winSize = win.getSize();
const displaySize =
electron.screen.getDisplayNearestPoint(windowPosition).bounds;
if (
x + winSize[0] < displaySize.x - 8 ||
x - winSize[0] > displaySize.x + displaySize.width ||
y < displaySize.y - 8 ||
y > displaySize.y + displaySize.height
) {
//Window is offscreen
if (is.dev()) {
console.log(
`Window tried to render offscreen, windowSize=${winSize}, displaySize=${displaySize}, position=${windowPosition}`
);
}
} else {
win.setPosition(x, y);
}
} }
if (windowMaximized) { if (windowMaximized) {
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;
@ -131,22 +176,47 @@ function createMainWindow() {
win.on("closed", onClosed); win.on("closed", onClosed);
win.on("move", () => { win.on("move", () => {
if (win.isMaximized()) return;
let position = win.getPosition(); let position = win.getPosition();
config.set("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] });
}
}); });
let winWasMaximized;
win.on("resize", () => { win.on("resize", () => {
const windowSize = win.getSize(); const windowSize = win.getSize();
config.set("window-maximized", win.isMaximized()); const isMaximized = win.isMaximized();
if (!win.isMaximized()) { if (winWasMaximized !== isMaximized) {
config.set("window-size", { winWasMaximized = isMaximized;
config.set("window-maximized", isMaximized);
}
const isPiPEnabled =
config.plugins.isEnabled("picture-in-picture") &&
config.plugins.getOptions("picture-in-picture")["isInPiP"];
if (!isMaximized && !isPiPEnabled) {
lateSave("window-size", {
width: windowSize[0], width: windowSize[0],
height: windowSize[1], height: windowSize[1],
}); });
} }
}); });
let savedTimeouts = {};
function lateSave(key, value) {
if (savedTimeouts[key]) clearTimeout(savedTimeouts[key]);
savedTimeouts[key] = setTimeout(() => {
config.set(key, value);
savedTimeouts[key] = undefined;
}, 1000)
}
win.webContents.on("render-process-gone", (event, webContents, details) => { win.webContents.on("render-process-gone", (event, webContents, details) => {
showUnresponsiveDialog(win, details); showUnresponsiveDialog(win, details);
}); });
@ -163,6 +233,32 @@ function createMainWindow() {
} }
app.once("browser-window-created", (event, win) => { app.once("browser-window-created", (event, win) => {
if (config.get("options.overrideUserAgent")) {
// User agents are from https://developers.whatismybrowser.com/useragents/explore/
const originalUserAgent = win.webContents.userAgent;
const userAgents = {
mac: "Mozilla/5.0 (Macintosh; Intel Mac OS X 12.1; rv:95.0) Gecko/20100101 Firefox/95.0",
windows: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0",
linux: "Mozilla/5.0 (Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0",
}
const updatedUserAgent =
is.macOS() ? userAgents.mac :
is.windows() ? userAgents.windows :
userAgents.linux;
win.webContents.userAgent = updatedUserAgent;
app.userAgentFallback = updatedUserAgent;
win.webContents.session.webRequest.onBeforeSendHeaders((details, cb) => {
// this will only happen if login failed, and "retry" was pressed
if (win.webContents.getURL().startsWith("https://accounts.google.com") && details.url.startsWith("https://accounts.google.com")) {
details.requestHeaders["User-Agent"] = originalUserAgent;
}
cb({ requestHeaders: details.requestHeaders });
});
}
setupSongInfo(win); setupSongInfo(win);
loadPlugins(win); loadPlugins(win);
@ -197,31 +293,6 @@ app.once("browser-window-created", (event, win) => {
event.preventDefault(); event.preventDefault();
}); });
win.webContents.on("will-navigate", (_, url) => {
if (url.startsWith("https://accounts.google.com")) {
// Force user-agent "Firefox Windows" for Google OAuth to work
// From https://github.com/firebase/firebase-js-sdk/issues/2478#issuecomment-571356751
// Only set on accounts.google.com, otherwise querySelectors in preload scripts fail (?)
// Uses custom user agent to Google alert with a correct device type (https://github.com/th-ch/youtube-music/issues/327)
// User agents are from https://developers.whatismybrowser.com/useragents/explore/
const userAgents = {
mac: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0",
windows: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0",
linux: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:90.0) Gecko/20100101 Firefox/90.0",
}
const userAgent =
is.macOS() ? userAgents.mac :
is.windows() ? userAgents.windows :
userAgents.linux;
win.webContents.session.webRequest.onBeforeSendHeaders((details, cb) => {
details.requestHeaders["User-Agent"] = userAgent;
cb({ requestHeaders: details.requestHeaders });
});
}
});
win.webContents.on( win.webContents.on(
"new-window", "new-window",
(e, url, frameName, disposition, options) => { (e, url, frameName, disposition, options) => {
@ -296,12 +367,6 @@ app.on("ready", () => {
mainWindow = createMainWindow(); mainWindow = createMainWindow();
setApplicationMenu(mainWindow); setApplicationMenu(mainWindow);
if (config.get("options.restartOnConfigChanges")) {
config.watch(() => {
app.relaunch();
app.exit();
});
}
setUpTray(app, mainWindow); setUpTray(app, mainWindow);
// Autostart at login // Autostart at login

100
menu.js
View File

@ -33,7 +33,7 @@ const mainMenuTemplate = (win) => {
const refreshMenu = () => { const refreshMenu = () => {
this.setApplicationMenu(win); this.setApplicationMenu(win);
if (inAppMenuActive) { if (inAppMenuActive) {
win.webContents.send("updateMenu", true); win.webContents.send("refreshMenu");
} }
} }
return [ return [
@ -51,6 +51,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),
], ],
}; };
@ -68,7 +69,7 @@ const mainMenuTemplate = (win) => {
type: "checkbox", type: "checkbox",
checked: config.get("options.autoUpdates"), checked: config.get("options.autoUpdates"),
click: (item) => { click: (item) => {
config.set("options.autoUpdates", item.checked); config.setMenuOption("options.autoUpdates", item.checked);
}, },
}, },
{ {
@ -76,15 +77,50 @@ const mainMenuTemplate = (win) => {
type: "checkbox", type: "checkbox",
checked: config.get("options.resumeOnStart"), checked: config.get("options.resumeOnStart"),
click: (item) => { click: (item) => {
config.set("options.resumeOnStart", item.checked); config.setMenuOption("options.resumeOnStart", item.checked);
}, },
}, },
{ {
label: "Remove upgrade button", label: "Visual Tweaks",
submenu: [
{
label: "Remove upgrade button",
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: "Single instance lock",
type: "checkbox", type: "checkbox",
checked: config.get("options.removeUpgradeButton"), checked: config.get("options.singleInstanceLock"),
click: (item) => { click: (item) => {
config.set("options.removeUpgradeButton", item.checked); config.setMenuOption("options.singleInstanceLock", item.checked);
if (item.checked && !app.hasSingleInstanceLock()) {
app.requestSingleInstanceLock();
} else if (!item.checked && app.hasSingleInstanceLock()) {
app.releaseSingleInstanceLock();
}
},
},
{
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()
@ -94,7 +130,7 @@ const mainMenuTemplate = (win) => {
type: "checkbox", type: "checkbox",
checked: config.get("options.hideMenu"), checked: config.get("options.hideMenu"),
click: (item) => { click: (item) => {
config.set("options.hideMenu", item.checked); config.setMenuOption("options.hideMenu", item.checked);
if (item.checked && !config.get("options.hideMenuWarned")) { if (item.checked && !config.get("options.hideMenuWarned")) {
dialog.showMessageBox(win, { dialog.showMessageBox(win, {
type: 'info', title: 'Hide Menu Enabled', type: 'info', title: 'Hide Menu Enabled',
@ -114,7 +150,7 @@ const mainMenuTemplate = (win) => {
type: "checkbox", type: "checkbox",
checked: config.get("options.startAtLogin"), checked: config.get("options.startAtLogin"),
click: (item) => { click: (item) => {
config.set("options.startAtLogin", item.checked); config.setMenuOption("options.startAtLogin", item.checked);
}, },
}, },
] ]
@ -127,8 +163,8 @@ const mainMenuTemplate = (win) => {
type: "radio", type: "radio",
checked: !config.get("options.tray"), checked: !config.get("options.tray"),
click: () => { click: () => {
config.set("options.tray", false); config.setMenuOption("options.tray", false);
config.set("options.appVisible", true); config.setMenuOption("options.appVisible", true);
}, },
}, },
{ {
@ -137,8 +173,8 @@ const mainMenuTemplate = (win) => {
checked: checked:
config.get("options.tray") && config.get("options.appVisible"), config.get("options.tray") && config.get("options.appVisible"),
click: () => { click: () => {
config.set("options.tray", true); config.setMenuOption("options.tray", true);
config.set("options.appVisible", true); config.setMenuOption("options.appVisible", true);
}, },
}, },
{ {
@ -147,8 +183,8 @@ const mainMenuTemplate = (win) => {
checked: checked:
config.get("options.tray") && !config.get("options.appVisible"), config.get("options.tray") && !config.get("options.appVisible"),
click: () => { click: () => {
config.set("options.tray", true); config.setMenuOption("options.tray", true);
config.set("options.appVisible", false); config.setMenuOption("options.appVisible", false);
}, },
}, },
{ type: "separator" }, { type: "separator" },
@ -157,7 +193,7 @@ const mainMenuTemplate = (win) => {
type: "checkbox", type: "checkbox",
checked: config.get("options.trayClickPlayPause"), checked: config.get("options.trayClickPlayPause"),
click: (item) => { click: (item) => {
config.set("options.trayClickPlayPause", item.checked); config.setMenuOption("options.trayClickPlayPause", item.checked);
}, },
}, },
], ],
@ -166,20 +202,28 @@ const mainMenuTemplate = (win) => {
{ {
label: "Advanced options", label: "Advanced options",
submenu: [ submenu: [
{ {
label: "Proxy", label: "Proxy",
type: "checkbox", type: "checkbox",
checked: !!config.get("options.proxy"), checked: !!config.get("options.proxy"),
click: (item) => { click: (item) => {
setProxy(item, win); setProxy(item, win);
}, },
}, },
{
label: "Override useragent",
type: "checkbox",
checked: config.get("options.overrideUserAgent"),
click: (item) => {
config.setMenuOption("options.overrideUserAgent", item.checked);
}
},
{ {
label: "Disable hardware acceleration", label: "Disable hardware acceleration",
type: "checkbox", type: "checkbox",
checked: config.get("options.disableHardwareAcceleration"), checked: config.get("options.disableHardwareAcceleration"),
click: (item) => { click: (item) => {
config.set("options.disableHardwareAcceleration", item.checked); config.setMenuOption("options.disableHardwareAcceleration", item.checked);
}, },
}, },
{ {
@ -187,7 +231,7 @@ const mainMenuTemplate = (win) => {
type: "checkbox", type: "checkbox",
checked: config.get("options.restartOnConfigChanges"), checked: config.get("options.restartOnConfigChanges"),
click: (item) => { click: (item) => {
config.set("options.restartOnConfigChanges", item.checked); config.setMenuOption("options.restartOnConfigChanges", item.checked);
}, },
}, },
{ {
@ -195,7 +239,7 @@ const mainMenuTemplate = (win) => {
type: "checkbox", type: "checkbox",
checked: config.get("options.autoResetAppCache"), checked: config.get("options.autoResetAppCache"),
click: (item) => { click: (item) => {
config.set("options.autoResetAppCache", item.checked); config.setMenuOption("options.autoResetAppCache", item.checked);
}, },
}, },
{ type: "separator" }, { type: "separator" },
@ -233,6 +277,8 @@ const mainMenuTemplate = (win) => {
{ role: "zoomIn" }, { role: "zoomIn" },
{ role: "zoomOut" }, { role: "zoomOut" },
{ role: "resetZoom" }, { role: "resetZoom" },
{ type: "separator" },
{ role: "togglefullscreen" },
], ],
}, },
{ {
@ -316,7 +362,7 @@ async function setProxy(item, win) {
}, win); }, win);
if (typeof output === "string") { if (typeof output === "string") {
config.set("options.proxy", output); config.setMenuOption("options.proxy", output);
item.checked = output !== ""; item.checked = output !== "";
} else { //user pressed cancel } else { //user pressed cancel
item.checked = !item.checked; //reset checkbox item.checked = !item.checked; //reset checkbox

View File

@ -1,7 +1,7 @@
{ {
"name": "youtube-music", "name": "youtube-music",
"productName": "YouTube Music", "productName": "YouTube Music",
"version": "1.14.0", "version": "1.17.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",
@ -15,10 +15,25 @@
"productName": "YouTube Music", "productName": "YouTube Music",
"mac": { "mac": {
"identity": null, "identity": null,
"files": [
"!plugins/taskbar-mediacontrol${/*}"
],
"target": [
{
"target": "dmg",
"arch": [
"x64",
"arm64"
]
}
],
"icon": "assets/generated/icons/mac/icon.icns" "icon": "assets/generated/icons/mac/icon.icns"
}, },
"win": { "win": {
"icon": "assets/generated/icons/win/icon.ico", "icon": "assets/generated/icons/win/icon.ico",
"files": [
"!plugins/touchbar${/*}"
],
"target": [ "target": [
"nsis", "nsis",
"portable" "portable"
@ -29,6 +44,9 @@
}, },
"linux": { "linux": {
"icon": "assets/generated/icons/png", "icon": "assets/generated/icons/png",
"files": [
"!plugins/{touchbar,taskbar-mediacontrol}${/*}"
],
"category": "AudioVideo", "category": "AudioVideo",
"target": [ "target": [
"AppImage", "AppImage",
@ -37,6 +55,15 @@
"deb", "deb",
"rpm" "rpm"
] ]
},
"snap": {
"slots": [
{
"mpris": {
"interface": "mpris"
}
}
]
} }
}, },
"scripts": { "scripts": {
@ -49,9 +76,11 @@
"clean": "rimraf dist", "clean": "rimraf dist",
"build": "yarn run clean && electron-builder --win --mac --linux", "build": "yarn run clean && electron-builder --win --mac --linux",
"build:linux": "yarn run clean && electron-builder --linux", "build:linux": "yarn run clean && electron-builder --linux",
"build:mac": "yarn run clean && electron-builder --mac", "build:mac": "yarn run clean && electron-builder --mac dmg:x64",
"build:mac:arm64": "yarn run clean && electron-builder --mac dmg:arm64",
"build:win": "yarn run clean && electron-builder --win", "build:win": "yarn run clean && electron-builder --win",
"lint": "xo", "lint": "xo",
"changelog": "auto-changelog",
"plugins": "yarn run plugin:adblocker", "plugins": "yarn run plugin:adblocker",
"plugin:adblocker": "rimraf plugins/adblocker/ad-blocker-engine.bin && node plugins/adblocker/blocker.js", "plugin:adblocker": "rimraf plugins/adblocker/ad-blocker-engine.bin && node plugins/adblocker/blocker.js",
"release:linux": "yarn run clean && electron-builder --linux -p always -c.snap.publish=github", "release:linux": "yarn run clean && electron-builder --linux -p always -c.snap.publish=github",
@ -63,46 +92,56 @@
"npm": "Please use yarn and not npm" "npm": "Please use yarn and not npm"
}, },
"dependencies": { "dependencies": {
"@cliqz/adblocker-electron": "^1.22.6", "@cliqz/adblocker-electron": "^1.23.7",
"@electron/remote": "^2.0.8",
"@ffmpeg/core": "^0.10.0", "@ffmpeg/core": "^0.10.0",
"@ffmpeg/ffmpeg": "^0.10.0", "@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",
"custom-electron-prompt": "^1.2.0", "chokidar": "^3.5.3",
"chokidar": "^3.5.2", "custom-electron-prompt": "^1.4.2",
"custom-electron-titlebar": "^3.2.7", "custom-electron-titlebar": "^4.1.0",
"discord-rpc": "^3.2.0", "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": "^7.0.3", "electron-store": "^8.0.1",
"electron-unhandled": "^3.0.2", "electron-unhandled": "^3.0.2",
"electron-updater": "^4.4.6", "electron-updater": "^4.6.3",
"filenamify": "^4.3.0", "filenamify": "^4.3.0",
"hark": "^1.2.3",
"html-to-text": "^8.2.0",
"md5": "^2.3.0", "md5": "^2.3.0",
"mpris-service": "^2.1.2", "mpris-service": "^2.1.2",
"node-fetch": "^2.6.2", "node-fetch": "^2.6.7",
"node-notifier": "^9.0.1", "node-notifier": "^10.0.1",
"ytdl-core": "^4.9.1", "ytdl-core": "^4.11.0",
"ytpl": "^2.2.3" "ytpl": "^2.3.0"
}, },
"devDependencies": { "devDependencies": {
"electron": "^12.2.2", "auto-changelog": "^2.4.0",
"electron-builder": "^22.10.5", "electron": "^17.0.0",
"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",
"get-port": "^5.1.1",
"jest": "^27.3.1", "jest": "^27.3.1",
"playwright": "^1.17.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"spectron": "^14.0.0",
"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": {
"hideCredit": true,
"package": true,
"unreleased": true,
"output": "changelog.md"
},
"xo": { "xo": {
"envs": [ "envs": [
"node", "node",

View File

@ -1,8 +1,10 @@
#nav-bar-background, #header.ytmusic-item-section-renderer { #nav-bar-background,
background: rgba(0, 0, 0, 0.3) !important; #header.ytmusic-item-section-renderer,
backdrop-filter: blur(18px) !important; ytmusic-tabs {
background: rgba(0, 0, 0, 0.3) !important;
backdrop-filter: blur(8px) !important;
} }
#nav-bar-divider { #nav-bar-divider {
display: none !important; display: none !important;
} }

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

@ -1,7 +1,14 @@
module.exports = () => { module.exports = () => {
document.addEventListener('apiLoaded', () => { document.addEventListener('apiLoaded', apiEvent => {
document.querySelector('video').addEventListener('loadeddata', e => { apiEvent.detail.addEventListener('videodatachange', name => {
e.target.pause(); if (name === 'dataloaded') {
apiEvent.detail.pauseVideo();
document.querySelector('video').ontimeupdate = e => {
e.target.pause();
}
} else {
document.querySelector('video').ontimeupdate = null;
}
}) })
}, { once: true, passive: true }) }, { once: true, passive: true })
}; };

View File

@ -1,11 +1,11 @@
const Discord = require("discord-rpc"); const Discord = require("discord-rpc");
const { dev } = require("electron-is"); const { dev } = require("electron-is");
const { dialog } = require("electron"); const { dialog, app } = require("electron");
const registerCallback = require("../../providers/song-info"); const registerCallback = require("../../providers/song-info");
// Application ID registered by @semvis123 // Application ID registered by @xn-oah
const clientId = "790655993809338398"; const clientId = "942539762227630162";
/** /**
* @typedef {Object} Info * @typedef {Object} Info
@ -70,7 +70,7 @@ let clearActivity;
*/ */
let updateActivity; let updateActivity;
module.exports = (win, {activityTimoutEnabled, activityTimoutTime, listenAlong}) => { module.exports = (win, { activityTimoutEnabled, activityTimoutTime, listenAlong }) => {
window = win; window = win;
// We get multiple events // We get multiple events
// Next song: PAUSE(n), PAUSE(n+1), PLAY(n+1) // Next song: PAUSE(n), PAUSE(n+1), PLAY(n+1)
@ -103,20 +103,17 @@ module.exports = (win, {activityTimoutEnabled, activityTimoutTime, listenAlong})
type: 2, // Listening, addressed in https://github.com/discordjs/RPC/pull/149 type: 2, // Listening, addressed in https://github.com/discordjs/RPC/pull/149
details: songInfo.title, details: songInfo.title,
state: songInfo.artist, state: songInfo.artist,
largeImageKey: "logo", largeImageKey: songInfo.imageSrc,
largeImageText: [ largeImageText: songInfo.album,
songInfo.uploadDate, buttons: listenAlong ? [
songInfo.views.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " views",
].join(' || '),
buttons: listenAlong ? [
{ label: "Listen Along", url: songInfo.url }, { label: "Listen Along", url: songInfo.url },
] : undefined, ] : undefined,
}; };
if (songInfo.isPaused) { if (songInfo.isPaused) {
// Add an idle icon to show that the song is paused // Add a paused icon to show that the song is paused
activityInfo.smallImageKey = "idle"; activityInfo.smallImageKey = "paused";
activityInfo.smallImageText = "idle/paused"; activityInfo.smallImageText = "Paused";
// Set start the timer so the activity gets cleared after a while if enabled // Set start the timer so the activity gets cleared after a while if enabled
if (activityTimoutEnabled) if (activityTimoutEnabled)
clearActivity = setTimeout(() => info.rpc.clearActivity().catch(console.error), activityTimoutTime ?? 10000); clearActivity = setTimeout(() => info.rpc.clearActivity().catch(console.error), activityTimoutTime ?? 10000);
@ -136,7 +133,7 @@ module.exports = (win, {activityTimoutEnabled, activityTimoutTime, listenAlong})
registerCallback(updateActivity); registerCallback(updateActivity);
connect(); connect();
}); });
win.on("close", () => module.exports.clear()); app.on('window-all-closed', module.exports.clear)
}; };
module.exports.clear = () => { module.exports.clear = () => {

View File

@ -1,5 +1,7 @@
const { setOptions } = require("../../config/plugins"); const prompt = require("custom-electron-prompt");
const { edit } = require("../../config");
const { setMenuOptions } = require("../../config/plugins");
const promptOptions = require("../../providers/prompt-options");
const { clear, connect, registerRefresh, isConnected } = require("./back"); const { clear, connect, registerRefresh, isConnected } = require("./back");
let hasRegisterred = false; let hasRegisterred = false;
@ -26,7 +28,7 @@ module.exports = (win, options, refreshMenu) => {
checked: options.activityTimoutEnabled, checked: options.activityTimoutEnabled,
click: (item) => { click: (item) => {
options.activityTimoutEnabled = item.checked; options.activityTimoutEnabled = item.checked;
setOptions('discord', options); setMenuOptions('discord', options);
}, },
}, },
{ {
@ -35,13 +37,29 @@ module.exports = (win, options, refreshMenu) => {
checked: options.listenAlong, checked: options.listenAlong,
click: (item) => { click: (item) => {
options.listenAlong = item.checked; options.listenAlong = item.checked;
setOptions('discord', options); setMenuOptions('discord', options);
}, },
}, },
{ {
label: "Set timeout time in config", label: "Set inactivity timeout",
// open config.json click: () => setInactivityTimeout(win, options),
click: edit,
}, },
]; ];
}; };
async function setInactivityTimeout(win, options) {
let output = await prompt({
title: 'Set Inactivity Timeout',
label: 'Enter inactivity timeout in seconds:',
value: Math.round((options.activityTimoutTime ?? 0) / 1e3),
type: "counter",
counterOptions: { minimum: 0, multiFire: true },
width: 450,
...promptOptions()
}, win)
if (output) {
options.activityTimoutTime = Math.round(output * 1e3);
setMenuOptions("discord", options);
}
}

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

@ -1,4 +1,4 @@
const { contextBridge } = require("electron"); const { ipcRenderer } = require("electron");
const { defaultConfig } = require("../../config"); const { defaultConfig } = require("../../config");
const { getSongMenu } = require("../../providers/dom-elements"); const { getSongMenu } = require("../../providers/dom-elements");
@ -13,15 +13,17 @@ const downloadButton = ElementFromFile(
); );
let pluginOptions = {}; let pluginOptions = {};
const observer = new MutationObserver((mutations, observer) => { const observer = new MutationObserver(() => {
if (!menu) { if (!menu) {
menu = getSongMenu(); menu = getSongMenu();
if (!menu) return;
} }
if (menu.contains(downloadButton)) return;
const menuUrl = document.querySelector('tp-yt-paper-listbox [tabindex="0"] #navigation-endpoint')?.href;
if (menuUrl && !menuUrl.includes('watch?')) return;
if (menu && !menu.contains(downloadButton)) { menu.prepend(downloadButton);
menu.prepend(downloadButton); progress = document.querySelector("#ytmcustom-download");
progress = document.querySelector("#ytmcustom-download");
}
}); });
const reinit = () => { const reinit = () => {
@ -43,10 +45,16 @@ global.download = () => {
let metadata; let metadata;
let videoUrl = getSongMenu() let videoUrl = getSongMenu()
// selector of first button which is always "Start Radio" // selector of first button which is always "Start Radio"
?.querySelector('ytmusic-menu-navigation-item-renderer.iron-selected[tabindex="0"] #navigation-endpoint') ?.querySelector('ytmusic-menu-navigation-item-renderer[tabindex="0"] #navigation-endpoint')
?.getAttribute("href"); ?.getAttribute("href");
if (videoUrl) { if (videoUrl) {
videoUrl = baseUrl + "/" + videoUrl; if (videoUrl.startsWith('watch?')) {
videoUrl = baseUrl + "/" + videoUrl;
}
if (videoUrl.includes('?playlist=')) {
ipcRenderer.send('download-playlist-request', videoUrl);
return;
}
metadata = null; metadata = null;
} else { } else {
metadata = global.songInfo; metadata = global.songInfo;
@ -78,10 +86,13 @@ global.download = () => {
function observeMenu(options) { function observeMenu(options) {
pluginOptions = { ...pluginOptions, ...options }; pluginOptions = { ...pluginOptions, ...options };
observer.observe(document, {
childList: true, document.addEventListener('apiLoaded', () => {
subtree: true, observer.observe(document.querySelector('ytmusic-popup-container'), {
}); childList: true,
subtree: true,
});
}, { once: true, passive: true })
} }
module.exports = observeMenu; module.exports = observeMenu;

View File

@ -1,102 +1,44 @@
const { existsSync, mkdirSync } = require("fs"); const { existsSync, mkdirSync } = require("fs");
const { join } = require("path"); const { join } = require("path");
const { URL } = require("url");
const { dialog } = require("electron"); const { dialog, ipcMain } = require("electron");
const is = require("electron-is"); const is = require("electron-is");
const ytpl = require("ytpl"); const ytpl = require("ytpl");
const chokidar = require('chokidar'); const chokidar = require('chokidar');
const filenamify = require('filenamify');
const { setOptions } = require("../../config/plugins"); const { setMenuOptions } = require("../../config/plugins");
const registerCallback = require("../../providers/song-info");
const { sendError } = require("./back"); const { sendError } = require("./back");
const { defaultMenuDownloadLabel, getFolder } = require("./utils"); const { defaultMenuDownloadLabel, getFolder, presets, setBadge } = require("./utils");
let downloadLabel = defaultMenuDownloadLabel; let downloadLabel = defaultMenuDownloadLabel;
let metadataURL = undefined; let playingUrl = undefined;
let callbackIsRegistered = false; let callbackIsRegistered = false;
// Playlist radio modifier needs to be cut from playlist ID
const INVALID_PLAYLIST_MODIFIER = 'RDAMPL';
const getPlaylistID = aURL => {
const result = aURL?.searchParams.get("list") || aURL?.searchParams.get("playlist");
if (result?.startsWith(INVALID_PLAYLIST_MODIFIER)) {
return result.slice(6)
}
return result;
};
module.exports = (win, options) => { module.exports = (win, options) => {
if (!callbackIsRegistered) { if (!callbackIsRegistered) {
registerCallback((info) => { ipcMain.on("video-src-changed", async (_, data) => {
metadataURL = info.url; playingUrl = JSON.parse(data)?.microformat?.microformatDataRenderer?.urlCanonical;
}); });
ipcMain.on("download-playlist-request", async (_event, url) => downloadPlaylist(url, win, options));
callbackIsRegistered = true; callbackIsRegistered = true;
} }
return [ return [
{ {
label: downloadLabel, label: downloadLabel,
click: async () => { click: () => downloadPlaylist(undefined, win, options),
const currentURL = metadataURL || win.webContents.getURL();
const playlistID = new URL(currentURL).searchParams.get("list");
if (!playlistID) {
sendError(win, new Error("No playlist ID found"));
return;
}
console.log(`trying to get playlist ID: '${playlistID}'`);
let playlist;
try {
playlist = await ytpl(playlistID, {
limit: options.playlistMaxItems || Infinity,
});
} catch (e) {
sendError(win, e);
return;
}
const playlistTitle = playlist.title;
const folder = getFolder(options.downloadFolder);
const playlistFolder = join(folder, playlistTitle);
if (existsSync(playlistFolder)) {
sendError(
win,
new Error(`The folder ${playlistFolder} already exists`)
);
return;
}
mkdirSync(playlistFolder, { recursive: true });
dialog.showMessageBox({
type: "info",
buttons: ["OK"],
title: "Started Download",
message: `Downloading Playlist "${playlistTitle}"`,
detail: `(${playlist.items.length} songs)`,
});
if (is.dev()) {
console.log(
`Downloading playlist "${playlistTitle}" (${playlist.items.length} songs)`
);
}
const steps = 1 / playlist.items.length;
let progress = 0;
win.setProgressBar(2); // starts with indefinite bar
let dirWatcher = chokidar.watch(playlistFolder);
dirWatcher.on('add', () => {
progress += steps;
if (progress >= 0.9999) {
win.setProgressBar(-1); // close progress bar
dirWatcher.close().then(() => dirWatcher = null);
} else {
win.setProgressBar(progress);
}
});
playlist.items.forEach((song) => {
win.webContents.send(
"downloader-download-playlist",
song.url,
playlistTitle,
options
);
});
},
}, },
{ {
label: "Choose download folder", label: "Choose download folder",
@ -107,9 +49,103 @@ module.exports = (win, options) => {
}); });
if (result) { if (result) {
options.downloadFolder = result[0]; options.downloadFolder = result[0];
setOptions("downloader", options); setMenuOptions("downloader", options);
} // else = user pressed cancel } // else = user pressed cancel
}, },
}, },
{
label: "Presets",
submenu: Object.keys(presets).map((preset) => ({
label: preset,
type: "radio",
click: () => {
options.preset = preset;
setMenuOptions("downloader", options);
},
checked: options.preset === preset || presets[preset] === undefined,
})),
},
]; ];
}; };
async function downloadPlaylist(givenUrl, win, options) {
if (givenUrl) {
try {
givenUrl = new URL(givenUrl);
} catch {
givenUrl = undefined;
};
}
const playlistId = getPlaylistID(givenUrl)
|| getPlaylistID(new URL(win.webContents.getURL()))
|| getPlaylistID(new URL(playingUrl));
if (!playlistId) {
sendError(win, new Error("No playlist ID found"));
return;
}
console.log(`trying to get playlist ID: '${playlistId}'`);
let playlist;
try {
playlist = await ytpl(playlistId, {
limit: options.playlistMaxItems || Infinity,
});
} catch (e) {
sendError(win, e);
return;
}
const safePlaylistTitle = filenamify(playlist.title, {replacement: ' '});
const folder = getFolder(options.downloadFolder);
const playlistFolder = join(folder, safePlaylistTitle);
if (existsSync(playlistFolder)) {
sendError(
win,
new Error(`The folder ${playlistFolder} already exists`)
);
return;
}
mkdirSync(playlistFolder, { recursive: true });
dialog.showMessageBox({
type: "info",
buttons: ["OK"],
title: "Started Download",
message: `Downloading Playlist "${playlist.title}"`,
detail: `(${playlist.items.length} songs)`,
});
if (is.dev()) {
console.log(
`Downloading playlist "${playlist.title}" - ${playlist.items.length} songs (${playlistId})`
);
}
win.setProgressBar(2); // starts with indefinite bar
let downloadCount = 0;
setBadge(playlist.items.length);
let dirWatcher = chokidar.watch(playlistFolder);
dirWatcher.on('add', () => {
downloadCount += 1;
if (downloadCount >= playlist.items.length) {
win.setProgressBar(-1); // close progress bar
setBadge(0); // close badge counter
dirWatcher.close().then(() => (dirWatcher = null));
} else {
win.setProgressBar(downloadCount / playlist.items.length);
setBadge(playlist.items.length - downloadCount);
}
});
playlist.items.forEach((song) => {
win.webContents.send(
"downloader-download-playlist",
song.url,
safePlaylistTitle,
options
);
});
}

View File

@ -1,7 +1,7 @@
const electron = require("electron"); const electron = require("electron");
const is = require('electron-is');
module.exports.getFolder = (customFolder) => module.exports.getFolder = customFolder => customFolder || electron.app.getPath("downloads");
customFolder || (electron.app || electron.remote.app).getPath("downloads");
module.exports.defaultMenuDownloadLabel = "Download playlist"; module.exports.defaultMenuDownloadLabel = "Download playlist";
const orderedQualityList = ["maxresdefault", "hqdefault", "mqdefault", "sdddefault"]; const orderedQualityList = ["maxresdefault", "hqdefault", "mqdefault", "sdddefault"];
@ -29,3 +29,18 @@ module.exports.cropMaxWidth = (image) => {
} }
return image; return image;
} }
// Presets for FFmpeg
module.exports.presets = {
"None (defaults to mp3)": undefined,
opus: {
extension: "opus",
ffmpegArgs: ["-acodec", "libopus"],
},
};
module.exports.setBadge = n => {
if (is.linux() || is.macOS()) {
electron.app.setBadgeCount(n);
}
}

View File

@ -3,6 +3,7 @@ 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");
@ -14,7 +15,7 @@ const ytdl = require("ytdl-core");
const { triggerAction, triggerActionSync } = require("../utils"); const { triggerAction, triggerActionSync } = require("../utils");
const { ACTIONS, CHANNEL } = require("./actions.js"); const { ACTIONS, CHANNEL } = require("./actions.js");
const { getFolder, urlToJPG } = require("./utils"); const { presets, urlToJPG } = require("./utils");
const { cleanupName } = require("../../providers/song-info"); const { cleanupName } = require("../../providers/song-info");
const { createFFmpeg } = FFmpeg; const { createFFmpeg } = FFmpeg;
@ -45,7 +46,7 @@ const downloadVideoToMP3 = async (
cleanupName(videoDetails?.author?.name) || cleanupName(videoDetails?.author?.name) ||
"", "",
title: videoDetails?.media?.song || videoDetails?.title || "", title: videoDetails?.media?.song || videoDetails?.title || "",
imageSrcYTPL: thumbnails ? imageSrcYTPL: thumbnails ?
urlToJPG(thumbnails[thumbnails.length - 1].url, videoDetails?.videoId) urlToJPG(thumbnails[thumbnails.length - 1].url, videoDetails?.videoId)
: "" : ""
} }
@ -112,8 +113,9 @@ const toMP3 = async (
existingMetadata = undefined, existingMetadata = undefined,
subfolder = "" subfolder = ""
) => { ) => {
const convertOptions = { ...presets[options.preset], ...options };
const safeVideoName = randomBytes(32).toString("hex"); const safeVideoName = randomBytes(32).toString("hex");
const extension = options.extension || "mp3"; const extension = convertOptions.extension || "mp3";
const releaseFFmpegMutex = await ffmpegMutex.acquire(); const releaseFFmpegMutex = await ffmpegMutex.acquire();
try { try {
@ -131,11 +133,11 @@ const toMP3 = async (
"-i", "-i",
safeVideoName, safeVideoName,
...getFFmpegMetadataArgs(metadata), ...getFFmpegMetadataArgs(metadata),
...(options.ffmpegArgs || []), ...(convertOptions.ffmpegArgs || []),
safeVideoName + "." + extension safeVideoName + "." + extension
); );
const folder = getFolder(options.downloadFolder); const folder = options.downloadFolder || remote.app.getPath("downloads");
const name = metadata.title const name = metadata.title
? `${metadata.artist ? `${metadata.artist} - ` : ""}${metadata.title}` ? `${metadata.artist ? `${metadata.artist} - ` : ""}${metadata.title}`
: videoName; : videoName;

View File

@ -0,0 +1,47 @@
// "Youtube Music fix volume ratio 0.4" by Marco Pfeiffer
// https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/
const exponentialVolume = () => {
// manipulation exponent, higher value = lower volume
// 3 is the value used by pulseaudio, which Barteks2x figured out this gist here: https://gist.github.com/Barteks2x/a4e189a36a10c159bb1644ffca21c02a
// 0.05 (or 5%) is the lowest you can select in the UI which with an exponent of 3 becomes 0.000125 or 0.0125%
const EXPONENT = 3;
const storedOriginalVolumes = new WeakMap();
const { get, set } = Object.getOwnPropertyDescriptor(
HTMLMediaElement.prototype,
"volume"
);
Object.defineProperty(HTMLMediaElement.prototype, "volume", {
get() {
const lowVolume = get.call(this);
const calculatedOriginalVolume = lowVolume ** (1 / EXPONENT);
// The calculated value has some accuracy issues which can lead to problems for implementations that expect exact values.
// To avoid this, I'll store the unmodified volume to return it when read here.
// This mostly solves the issue, but the initial read has no stored value and the volume can also change though external influences.
// To avoid ill effects, I check if the stored volume is somewhere in the same range as the calculated volume.
const storedOriginalVolume = storedOriginalVolumes.get(this);
const storedDeviation = Math.abs(
storedOriginalVolume - calculatedOriginalVolume
);
const originalVolume =
storedDeviation < 0.01
? storedOriginalVolume
: calculatedOriginalVolume;
return originalVolume;
},
set(originalVolume) {
const lowVolume = originalVolume ** EXPONENT;
storedOriginalVolumes.set(this, originalVolume);
set.call(this, lowVolume);
},
});
};
module.exports = () =>
document.addEventListener("apiLoaded", exponentialVolume, {
once: true,
passive: true,
});

View File

@ -5,14 +5,19 @@ const electronLocalshortcut = require("electron-localshortcut");
const config = require("../../config"); const config = require("../../config");
const { injectCSS } = require("../utils"); const { injectCSS } = require("../utils");
const { setupTitlebar, attachTitlebarToWindow } = require('custom-electron-titlebar/main');
setupTitlebar();
//tracks menu visibility //tracks menu visibility
let visible = true; let visible = !config.get("options.hideMenu");
module.exports = (win) => { module.exports = (win) => {
// css for custom scrollbar + disable drag area(was causing bugs) // css for custom scrollbar + disable drag area(was causing bugs)
injectCSS(win.webContents, path.join(__dirname, "style.css")); injectCSS(win.webContents, path.join(__dirname, "style.css"));
win.once("ready-to-show", () => { win.once("ready-to-show", () => {
attachTitlebarToWindow(win);
//register keyboard shortcut && hide menu if hideMenu is enabled //register keyboard shortcut && hide menu if hideMenu is enabled
if (config.get("options.hideMenu")) { if (config.get("options.hideMenu")) {
electronLocalshortcut.register(win, "Esc", () => { electronLocalshortcut.register(win, "Esc", () => {
@ -21,13 +26,8 @@ module.exports = (win) => {
} }
}); });
win.webContents.once("did-finish-load", () => {
// fix bug with menu not applying on start when no internet connection available
setMenuVisibility(!config.get("options.hideMenu"));
});
function setMenuVisibility(value) { function setMenuVisibility(value) {
visible = value; visible = value;
win.webContents.send("updateMenu", visible); win.webContents.send("refreshMenu", visible);
} }
}; };

View File

@ -1,20 +1,36 @@
const { remote, ipcRenderer } = require("electron"); const { ipcRenderer } = require("electron");
const config = require("../../config");
const customTitlebar = require("custom-electron-titlebar"); const { Titlebar, Color } = require("custom-electron-titlebar");
function $(selector) { return document.querySelector(selector); } function $(selector) { return document.querySelector(selector); }
module.exports = () => { module.exports = (options) => {
const bar = new customTitlebar.Titlebar({ let visible = !config.get("options.hideMenu");
backgroundColor: customTitlebar.Color.fromHex("#050505"), const bar = new Titlebar({
itemBackgroundColor: customTitlebar.Color.fromHex("#121212"), backgroundColor: Color.fromHex("#050505"),
itemBackgroundColor: Color.fromHex("#1d1d1d"),
svgColor: Color.WHITE,
menu: visible ? undefined : null
}); });
bar.updateTitle(" "); bar.updateTitle(" ");
document.title = "Youtube Music"; document.title = "Youtube Music";
ipcRenderer.on("updateMenu", function (_event, showMenu) { const hideIcon = hide => $('.cet-window-icon').style.display = hide ? 'none' : 'flex';
bar.updateMenu(showMenu ? remote.Menu.getApplicationMenu() : null);
if (options.hideIcon) hideIcon(true);
ipcRenderer.on("refreshMenu", (_, showMenu) => {
if (showMenu === undefined && !visible) return;
if (showMenu === false) {
bar.updateMenu(null);
visible = false;
} else {
bar.refreshMenu();
visible = true;
}
}); });
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

@ -4,10 +4,13 @@
font-size: 14px !important; font-size: 14px !important;
} }
/* fixes nav-bar-background opacity bug and allows clicking scrollbar through it */ /* fixes nav-bar-background opacity bug, reposition it, and allows clicking scrollbar through it */
#nav-bar-background { #nav-bar-background {
opacity: 1 !important; opacity: 1 !important;
pointer-events: none; pointer-events: none !important;
position: sticky !important;
top: 0 !important;
height: 75px !important;
} }
/* remove window dragging for nav bar (conflict with titlebar drag) */ /* remove window dragging for nav bar (conflict with titlebar drag) */
@ -17,9 +20,10 @@ ytmusic-pivot-bar-item-renderer {
-webkit-app-region: unset !important; -webkit-app-region: unset !important;
} }
/* move up item selection renderer by 13 px */ /* move up item selection renderers */
ytmusic-item-section-renderer.stuck #header.ytmusic-item-section-renderer { ytmusic-item-section-renderer.stuck #header.ytmusic-item-section-renderer,
top: calc(var(--ytmusic-nav-bar-height) - 13px) !important; ytmusic-tabs.stuck {
top: calc(var(--ytmusic-nav-bar-height) - 15px) !important;
} }
/* fix weird positioning in search screen*/ /* fix weird positioning in search screen*/
@ -28,8 +32,7 @@ ytmusic-header-renderer.ytmusic-search-page {
} }
/* Move navBar downwards */ /* Move navBar downwards */
ytmusic-nav-bar[slot="nav-bar"], ytmusic-nav-bar[slot="nav-bar"] {
#nav-bar-background {
top: 17px !important; top: 17px !important;
} }
@ -54,10 +57,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;
@ -68,3 +71,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,41 +13,59 @@ 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
)}`;
let response = await fetch(
`https://genius.com/api/search/multi?per_page=5&q=${encodeURI(
queryString
)}`
);
if (!response.ok) {
event.returnValue = null;
return;
}
const info = await response.json();
let url = "";
try {
url = info.response.sections.filter(
(section) => section.type === "song"
)[0].hits[0].result.url;
} catch {
event.returnValue = null;
return;
}
if (is.dev()) {
console.log("Fetching lyrics from Genius:", url);
}
response = await fetch(url);
if (!response.ok) {
event.returnValue = null;
return;
}
event.returnValue = await response.text();
}); });
}; };
const fetchFromGenius = async (metadata) => {
const queryString = `${cleanupName(metadata.artist)} ${cleanupName(
metadata.title
)}`;
let response = await fetch(
`https://genius.com/api/search/multi?per_page=5&q=${encodeURI(queryString)}`
);
if (!response.ok) {
return null;
}
const info = await response.json();
let url = "";
try {
url = info.response.sections.filter((section) => section.type === "song")[0]
.hits[0].result.url;
} catch {
return null;
}
if (is.dev()) {
console.log("Fetching lyrics from Genius:", url);
}
response = await fetch(url);
if (!response.ok) {
return null;
}
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

@ -3,59 +3,47 @@ const is = require("electron-is");
module.exports = () => { module.exports = () => {
ipcRenderer.on("update-song-info", (_, extractedSongInfo) => { ipcRenderer.on("update-song-info", (_, extractedSongInfo) => {
const lyricsTab = document.querySelector('tp-yt-paper-tab[tabindex="-1"]'); const tabList = document.querySelectorAll("tp-yt-paper-tab");
const tabs = {
upNext: tabList[0],
lyrics: tabList[1],
discover: tabList[2],
}
// Check if disabled // Check if disabled
if (!lyricsTab || !lyricsTab.hasAttribute("disabled")) { if (!tabs.lyrics?.hasAttribute("disabled")) {
return; return;
} }
const html = ipcRenderer.sendSync( let hasLyrics = true;
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
checkLyricsContainer(() => {
hasLyrics = false;
setTabsOnclick(undefined);
});
return; return;
} else if (is.dev()) { }
if (is.dev()) {
console.log("Fetched lyrics from Genius"); console.log("Fetched lyrics from Genius");
} }
const wrapper = document.createElement("div"); enableLyricsTab();
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;
}
lyricsTab.removeAttribute("disabled"); setTabsOnclick(enableLyricsTab);
lyricsTab.removeAttribute("aria-disabled");
document.querySelector("tp-yt-paper-tab").onclick = () => {
lyricsTab.removeAttribute("disabled");
lyricsTab.removeAttribute("aria-disabled");
};
lyricsTab.onclick = () => { checkLyricsContainer();
tabs.lyrics.onclick = () => {
const tabContainer = document.querySelector("ytmusic-tab-renderer"); const tabContainer = document.querySelector("ytmusic-tab-renderer");
const observer = new MutationObserver((_, observer) => { const observer = new MutationObserver((_, observer) => {
const lyricsContainer = document.querySelector( checkLyricsContainer(() => observer.disconnect());
'[page-type="MUSIC_PAGE_TYPE_TRACK_LYRICS"] > ytmusic-message-renderer'
);
if (lyricsContainer) {
lyricsContainer.innerHTML = `<div id="contents" class="style-scope ytmusic-section-list-renderer genius-lyrics">
${lyrics}
<yt-formatted-string class="footer style-scope ytmusic-description-shelf-renderer">Source&nbsp;: Genius</yt-formatted-string>
</div>`;
observer.disconnect();
}
}); });
observer.observe(tabContainer, { observer.observe(tabContainer, {
attributes: true, attributes: true,
@ -63,5 +51,44 @@ module.exports = () => {
subtree: true, subtree: true,
}); });
}; };
function checkLyricsContainer(callback = () => {}) {
const lyricsContainer = document.querySelector(
'[page-type="MUSIC_PAGE_TYPE_TRACK_LYRICS"] > ytmusic-message-renderer'
);
if (lyricsContainer) {
callback();
setLyrics(lyricsContainer)
}
}
function setLyrics(lyricsContainer) {
lyricsContainer.innerHTML = `<div id="contents" class="style-scope ytmusic-section-list-renderer description ytmusic-description-shelf-renderer genius-lyrics">
${
hasLyrics
? lyrics.replace(/(?:\r\n|\r|\n)/g, "<br/>")
: "Could not retrieve lyrics from genius"
}
</div>
<yt-formatted-string class="footer style-scope ytmusic-description-shelf-renderer" style="align-self: baseline"></yt-formatted-string>`;
if (hasLyrics) {
lyricsContainer.querySelector('.footer').textContent = 'Source: Genius';
enableLyricsTab();
}
}
function setTabsOnclick(callback) {
for (const tab of [tabs.upNext, tabs.discover]) {
if (tab) {
tab.onclick = callback;
}
}
}
function enableLyricsTab() {
tabs.lyrics.removeAttribute("disabled");
tabs.lyrics.removeAttribute("aria-disabled");
}
}); });
}; };

View File

@ -5,3 +5,8 @@
pointer-events: none; pointer-events: none;
text-decoration: none; text-decoration: none;
} }
.description {
font-size: 1.1vw !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

@ -3,8 +3,6 @@ const is = require("electron-is");
const registerCallback = require("../../providers/song-info"); const registerCallback = require("../../providers/song-info");
const { notificationImage } = require("./utils"); const { notificationImage } = require("./utils");
const setupInteractive = require("./interactive")
const notify = (info, options) => { const notify = (info, options) => {
// Fill the notification with content // Fill the notification with content
@ -41,6 +39,6 @@ const setup = (options) => {
module.exports = (win, options) => { module.exports = (win, options) => {
// Register the callback for new song information // Register the callback for new song information
is.windows() && options.interactive ? is.windows() && options.interactive ?
setupInteractive(win, options.unpauseNotification) : require("./interactive")(win, options.unpauseNotification) :
setup(options); setup(options);
}; };

View File

@ -1,7 +1,10 @@
const { notificationImage, icons } = require("./utils"); const { notificationImage, icons } = require("./utils");
const getSongControls = require('../../providers/song-controls'); const getSongControls = require('../../providers/song-controls');
const registerCallback = require("../../providers/song-info"); const registerCallback = require("../../providers/song-info");
const notifier = require("node-notifier"); const is = require("electron-is");
const WindowsToaster = require('node-notifier').WindowsToaster;
const notifier = new WindowsToaster({ withFallback: true });
//store song controls reference on launch //store song controls reference on launch
let controls; let controls;
@ -17,11 +20,11 @@ module.exports = (win, unpauseNotification) => {
// Register songInfoCallback // Register songInfoCallback
registerCallback(songInfo => { registerCallback(songInfo => {
if (!songInfo.isPaused && (songInfo.url !== currentUrl || notificationOnUnpause)) { if (!songInfo.isPaused && (songInfo.url !== currentUrl || notificationOnUnpause)) {
currentUrl = songInfo.url; currentUrl = songInfo.url;
sendToaster(songInfo); sendToaster(songInfo);
} }
}); });
win.webContents.once("closed", () => { win.webContents.once("closed", () => {
deleteNotification() deleteNotification()
@ -48,7 +51,7 @@ function sendToaster(songInfo) {
//download image and get path //download image and get path
let imgSrc = notificationImage(songInfo, true); let imgSrc = notificationImage(songInfo, true);
toDelete = { toDelete = {
//app id undefined - will break buttons appID: is.dev() ? undefined : "com.github.th-ch.youtube-music",
title: songInfo.title || "Playing", title: songInfo.title || "Playing",
message: songInfo.artist, message: songInfo.artist,
id: parseInt(Math.random() * 1000000, 10), id: parseInt(Math.random() * 1000000, 10),

View File

@ -1,4 +1,4 @@
const { setOptions } = require("../../config/plugins"); const { setMenuOptions } = require("../../config/plugins");
const path = require("path"); const path = require("path");
const { app } = require("electron"); const { app } = require("electron");
const fs = require("fs"); const fs = require("fs");
@ -15,7 +15,7 @@ module.exports.icons = {
module.exports.setOption = (options, option, value) => { module.exports.setOption = (options, option, value) => {
options[option] = value; options[option] = value;
setOptions("notifications", options) setMenuOptions("notifications", options)
} }
module.exports.urgencyLevels = [ module.exports.urgencyLevels = [

View File

@ -0,0 +1,79 @@
const path = require("path");
const { app, ipcMain } = require("electron");
const { setOptions } = require("../../config/plugins");
const { injectCSS } = require("../utils");
let isInPiPMode = false;
let originalPosition;
let originalSize;
const pipPosition = [10, 10];
const pipSize = [450, 275];
const togglePiP = async (win) => {
isInPiPMode = !isInPiPMode;
setOptions("picture-in-picture", { isInPiP: isInPiPMode });
if (isInPiPMode) {
originalPosition = win.getPosition();
originalSize = win.getSize();
win.webContents.on("before-input-event", blockShortcutsInPiP);
win.setFullScreenable(false);
await win.webContents.executeJavaScript(
// Go fullscreen
`
if (!document.querySelector("ytmusic-player-page").playerPageOpen_) {
document.querySelector(".toggle-player-page-button").click();
}
document.querySelector(".fullscreen-button").click();
document.querySelector("ytmusic-player-bar").classList.add("pip");
`
);
win.setFullScreenable(true);
app.dock?.hide();
win.setVisibleOnAllWorkspaces(true, {
visibleOnFullScreen: true,
});
app.dock?.show();
win.setAlwaysOnTop(true, "screen-saver", 1);
} else {
win.webContents.removeListener("before-input-event", blockShortcutsInPiP);
await win.webContents.executeJavaScript(
// Exit fullscreen
`
document.querySelector(".exit-fullscreen-button").click();
document.querySelector("ytmusic-player-bar").classList.remove("pip");
`
);
win.setVisibleOnAllWorkspaces(false);
win.setAlwaysOnTop(false);
}
const [x, y] = isInPiPMode ? pipPosition : originalPosition;
const [w, h] = isInPiPMode ? pipSize : originalSize;
win.setPosition(x, y);
win.setSize(w, h);
win.setWindowButtonVisibility?.(!isInPiPMode);
};
module.exports = (win) => {
injectCSS(win.webContents, path.join(__dirname, "style.css"));
ipcMain.on("picture-in-picture", async () => {
await togglePiP(win);
});
};
const blockShortcutsInPiP = (event, input) => {
const blockedShortcuts = ["f", "escape"];
if (blockedShortcuts.includes(input.key.toLowerCase())) {
event.preventDefault();
}
};

View File

@ -0,0 +1,42 @@
const { ipcRenderer } = require("electron");
const { getSongMenu } = require("../../providers/dom-elements");
const { ElementFromFile, templatePath } = require("../utils");
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 = document.querySelector(
'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");
};
function observeMenu(options) {
document.addEventListener(
"apiLoaded",
() => {
observer.observe(document.querySelector("ytmusic-popup-container"), {
childList: true,
subtree: true,
});
},
{ once: true, passive: true }
);
}
module.exports = observeMenu;

View File

@ -0,0 +1,11 @@
ytmusic-player-bar.pip svg,
ytmusic-player-bar.pip yt-formatted-string {
filter: drop-shadow(2px 4px 6px black);
color: white;
}
ytmusic-player-bar.pip ytmusic-player-expanding-menu {
border-radius: 30px;
background-color: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(5px) brightness(20%);
}

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

@ -5,15 +5,13 @@ function $(selector) { return document.querySelector(selector); }
const slider = ElementFromFile(templatePath(__dirname, "slider.html")); const slider = ElementFromFile(templatePath(__dirname, "slider.html"));
const roundToTwo = (n) => Math.round(n * 1e2) / 1e2; const roundToTwo = n => Math.round(n * 1e2) / 1e2;
const MIN_PLAYBACK_SPEED = 0.07; const MIN_PLAYBACK_SPEED = 0.07;
const MAX_PLAYBACK_SPEED = 16; const MAX_PLAYBACK_SPEED = 16;
let playbackSpeed = 1; let playbackSpeed = 1;
const computePlayBackSpeed = (playbackSpeedPercentage) => playbackSpeedPercentage || MIN_PLAYBACK_SPEED;
const updatePlayBackSpeed = () => { const updatePlayBackSpeed = () => {
$('video').playbackRate = playbackSpeed; $('video').playbackRate = playbackSpeed;
@ -32,7 +30,7 @@ const observePopupContainer = () => {
menu = getSongMenu(); menu = getSongMenu();
} }
if (menu && !menu.contains(slider)) { if (menu && menu.lastElementChild.lastElementChild.innerText.startsWith('Stats') && !menu.contains(slider)) {
menu.prepend(slider); menu.prepend(slider);
if (!observingSlider) { if (!observingSlider) {
setupSliderListener(); setupSliderListener();
@ -49,7 +47,7 @@ const observePopupContainer = () => {
const observeVideo = () => { const observeVideo = () => {
$('video').addEventListener('ratechange', forcePlaybackRate) $('video').addEventListener('ratechange', forcePlaybackRate)
$('video').addEventListener('loadeddata', forcePlaybackRate) $('video').addEventListener('srcChanged', forcePlaybackRate)
} }
const setupWheelListener = () => { const setupWheelListener = () => {
@ -71,8 +69,8 @@ const setupWheelListener = () => {
} }
function setupSliderListener() { function setupSliderListener() {
$('#playback-speed-slider').addEventListener('immediate-value-changed', () => { $('#playback-speed-slider').addEventListener('immediate-value-changed', e => {
playbackSpeed = computePlayBackSpeed($('#playback-speed-slider #sliderBar').value); playbackSpeed = e.detail.value || MIN_PLAYBACK_SPEED;
if (isNaN(playbackSpeed)) { if (isNaN(playbackSpeed)) {
playbackSpeed = 1; playbackSpeed = 1;
} }
@ -87,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,15 @@
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; module.exports = (win) => {
enabled = true;
injectCSS(win.webContents, path.join(__dirname, "volume-hud.css"));
}
module.exports.enabled = () => enabled; module.exports.enabled = () => enabled;

View File

@ -1,6 +1,7 @@
const { ipcRenderer, remote } = require("electron"); const { ipcRenderer } = require("electron");
const { globalShortcut } = require('@electron/remote');
const { setOptions } = require("../../config/plugins"); const { setOptions, setMenuOptions, isEnabled } = require("../../config/plugins");
function $(selector) { return document.querySelector(selector); } function $(selector) { return document.querySelector(selector); }
let api; let api;
@ -12,6 +13,8 @@ module.exports = (options) => {
}, { once: true, passive: true }) }, { once: true, passive: true })
}; };
module.exports.moveVolumeHud = moveVolumeHud;
/** Restore saved volume and setup tooltip */ /** Restore saved volume and setup tooltip */
function firstRun(options) { function firstRun(options) {
if (typeof options.savedVolume === "number") { if (typeof options.savedVolume === "number") {
@ -33,25 +36,46 @@ function firstRun(options) {
injectVolumeHud(noVid); injectVolumeHud(noVid);
if (!noVid) { if (!noVid) {
setupVideoPlayerOnwheel(options); setupVideoPlayerOnwheel(options);
if (!isEnabled('video-toggle')) {
//video-toggle handles hud positioning on its own
const videoMode = () => api.getPlayerResponse().videoDetails?.musicVideoType !== 'MUSIC_VIDEO_TYPE_ATV';
$("video").addEventListener("srcChanged", () => moveVolumeHud(videoMode()));
}
} }
// Change options from renderer to keep sync
ipcRenderer.on("setOptions", (_event, newOptions = {}) => {
Object.assign(options, newOptions)
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"; 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;"; 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>`)
} }
} }
let hudMoveTimeout;
function moveVolumeHud(showVideo) {
clearTimeout(hudMoveTimeout);
const volumeHud = $('#volumeHud');
if (!volumeHud) return;
hudMoveTimeout = setTimeout(() => {
volumeHud.style.top = showVideo ? `${($('ytmusic-player').clientHeight - $('video').clientHeight) / 2}px` : 0;
}, 250)
}
let hudFadeTimeout; let hudFadeTimeout;
function showVolumeHud(volume) { function showVolumeHud(volume) {
@ -93,7 +117,7 @@ function writeOptions(options) {
writeTimeout = setTimeout(() => { writeTimeout = setTimeout(() => {
setOptions("precise-volume", options); setOptions("precise-volume", options);
writeTimeout = null; writeTimeout = null;
}, 1500) }, 1000)
} }
/** 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*/
@ -142,7 +166,7 @@ function setupSliderObserver(options) {
/** if (toIncrease = false) then volume decrease */ /** if (toIncrease = false) then volume decrease */
function changeVolume(toIncrease, options) { function changeVolume(toIncrease, options) {
// Apply volume change if valid // Apply volume change if valid
const steps = (options.steps || 1); const steps = Number(options.steps || 1);
api.setVolume(toIncrease ? api.setVolume(toIncrease ?
Math.min(api.getVolume() + steps, 100) : Math.min(api.getVolume() + steps, 100) :
Math.max(api.getVolume() - steps, 0)); Math.max(api.getVolume() - steps, 0));
@ -163,8 +187,12 @@ function changeVolume(toIncrease, options) {
function updateVolumeSlider(options) { function updateVolumeSlider(options) {
// 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;
@ -202,48 +230,26 @@ function setTooltip(volume) {
function setupGlobalShortcuts(options) { function setupGlobalShortcuts(options) {
if (options.globalShortcuts.volumeUp) { if (options.globalShortcuts.volumeUp) {
remote.globalShortcut.register((options.globalShortcuts.volumeUp), () => changeVolume(true, options)); globalShortcut.register((options.globalShortcuts.volumeUp), () => changeVolume(true, options));
} }
if (options.globalShortcuts.volumeDown) { if (options.globalShortcuts.volumeDown) {
remote.globalShortcut.register((options.globalShortcuts.volumeDown), () => changeVolume(false, options)); globalShortcut.register((options.globalShortcuts.volumeDown), () => changeVolume(false, options));
} }
} }
function setupLocalArrowShortcuts(options) { function setupLocalArrowShortcuts(options) {
if (options.arrowsShortcut) { if (options.arrowsShortcut) {
addListener(); window.addEventListener('keydown', (event) => {
} switch (event.code) {
case "ArrowUp":
// Change options from renderer to keep sync event.preventDefault();
ipcRenderer.on("setArrowsShortcut", (_event, isEnabled) => { changeVolume(true, options);
options.arrowsShortcut = isEnabled; break;
setOptions("precise-volume", options); case "ArrowDown":
// This allows changing this setting without restarting app event.preventDefault();
if (isEnabled) { changeVolume(false, options);
addListener(); break;
} else { }
removeListener(); });
}
});
function addListener() {
window.addEventListener('keydown', callback);
}
function removeListener() {
window.removeEventListener("keydown", callback);
}
function callback(event) {
switch (event.code) {
case "ArrowUp":
event.preventDefault();
changeVolume(true, options);
break;
case "ArrowDown":
event.preventDefault();
changeVolume(false, options);
break;
}
} }
} }

View File

@ -1,8 +1,19 @@
const { enabled } = require("./back"); const { enabled } = require("./back");
const { setOptions } = require("../../config/plugins"); const { setMenuOptions } = require("../../config/plugins");
const prompt = require("custom-electron-prompt"); const prompt = require("custom-electron-prompt");
const promptOptions = require("../../providers/prompt-options"); const promptOptions = require("../../providers/prompt-options");
function changeOptions(changedOptions, options, win) {
for (option in changedOptions) {
options[option] = changedOptions[option];
}
// Dynamically change setting if plugin is enabled
if (enabled()) {
win.webContents.send("setOptions", changedOptions);
} else { // Fallback to usual method if disabled
setMenuOptions("precise-volume", options);
}
}
module.exports = (win, options) => [ module.exports = (win, options) => [
{ {
@ -10,13 +21,7 @@ module.exports = (win, options) => [
type: "checkbox", type: "checkbox",
checked: !!options.arrowsShortcut, checked: !!options.arrowsShortcut,
click: item => { click: item => {
// Dynamically change setting if plugin is enabled changeOptions({ arrowsShortcut: item.checked }, options, win);
if (enabled()) {
win.webContents.send("setArrowsShortcut", item.checked);
} else { // Fallback to usual method if disabled
options.arrowsShortcut = item.checked;
setOptions("precise-volume", options);
}
} }
}, },
{ {
@ -46,8 +51,7 @@ async function promptVolumeSteps(win, options) {
}, win) }, win)
if (output || output === 0) { // 0 is somewhat valid if (output || output === 0) { // 0 is somewhat valid
options.steps = output; changeOptions({ steps: output}, options, win);
setOptions("precise-volume", options);
} }
} }
@ -64,11 +68,11 @@ async function promptGlobalShortcuts(win, options, item) {
}, win) }, win)
if (output) { if (output) {
let newGlobalShortcuts = {};
for (const { value, accelerator } of output) { for (const { value, accelerator } of output) {
options.globalShortcuts[value] = accelerator; newGlobalShortcuts[value] = accelerator;
} }
changeOptions({ globalShortcuts: newGlobalShortcuts }, options, win);
setOptions("precise-volume", options);
item.checked = !!options.globalShortcuts.volumeUp || !!options.globalShortcuts.volumeDown; item.checked = !!options.globalShortcuts.volumeUp || !!options.globalShortcuts.volumeDown;
} else { } else {

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

@ -1,5 +1,5 @@
const { ElementFromFile, templatePath } = require("../utils"); const { ElementFromFile, templatePath } = require("../utils");
const dialog = require('electron').remote.dialog const { dialog } = require('@electron/remote');
function $(selector) { return document.querySelector(selector); } function $(selector) { return document.querySelector(selector); }

View File

@ -2,10 +2,7 @@ const { globalShortcut } = require("electron");
const is = require("electron-is"); const is = require("electron-is");
const electronLocalshortcut = require("electron-localshortcut"); const electronLocalshortcut = require("electron-localshortcut");
const getSongControls = require("../../providers/song-controls"); const getSongControls = require("../../providers/song-controls");
const { setupMPRIS } = require("./mpris"); const registerMPRIS = require("./mpris");
const registerCallback = require("../../providers/song-info");
let player;
function _registerGlobalShortcut(webContents, shortcut, action) { function _registerGlobalShortcut(webContents, shortcut, action) {
globalShortcut.register(shortcut, () => { globalShortcut.register(shortcut, () => {
@ -31,54 +28,8 @@ function registerShortcuts(win, options) {
_registerLocalShortcut(win, "CommandOrControl+F", search); _registerLocalShortcut(win, "CommandOrControl+F", search);
_registerLocalShortcut(win, "CommandOrControl+L", search); _registerLocalShortcut(win, "CommandOrControl+L", search);
registerCallback(songInfo => {
if (player) {
player.metadata = {
'mpris:length': songInfo.songDuration * 60 * 1000 * 1000, // In microseconds
'mpris:artUrl': songInfo.imageSrc,
'xesam:title': songInfo.title,
'xesam:artist': songInfo.artist
};
if (!songInfo.isPaused) {
player.playbackStatus = "Playing"
}
}
}
)
if (is.linux()) { if (is.linux()) registerMPRIS(win);
try {
const MPRISPlayer = setupMPRIS();
MPRISPlayer.on("raise", () => {
win.setSkipTaskbar(false);
win.show();
});
MPRISPlayer.on("play", () => {
if (MPRISPlayer.playbackStatus !== 'Playing') {
MPRISPlayer.playbackStatus = 'Playing';
playPause()
}
});
MPRISPlayer.on("pause", () => {
if (MPRISPlayer.playbackStatus !== 'Paused') {
MPRISPlayer.playbackStatus = 'Paused';
playPause()
}
});
MPRISPlayer.on("next", () => {
next()
});
MPRISPlayer.on("previous", () => {
previous()
});
player = MPRISPlayer
} catch (e) {
console.warn("Error in MPRIS", e);
}
}
const { global, local } = options; const { global, local } = options;
const shortcutOptions = { global, local }; const shortcutOptions = { global, local };

View File

@ -1,4 +1,4 @@
const { setOptions } = require("../../config/plugins"); const { setMenuOptions } = require("../../config/plugins");
const prompt = require("custom-electron-prompt"); const prompt = require("custom-electron-prompt");
const promptOptions = require("../../providers/prompt-options"); const promptOptions = require("../../providers/prompt-options");
@ -20,7 +20,7 @@ function setOption(options, key = null, newValue = null) {
options[key] = newValue; options[key] = newValue;
} }
setOptions("shortcuts", options); setMenuOptions("shortcuts", options);
} }
// Helper function for keybind prompt // Helper function for keybind prompt

View File

@ -1,4 +1,7 @@
const mpris = require("mpris-service"); const mpris = require("mpris-service");
const { ipcMain } = require("electron");
const registerCallback = require("../../providers/song-info");
const getSongControls = require("../../providers/song-controls");
function setupMPRIS() { function setupMPRIS() {
const player = mpris({ const player = mpris({
@ -14,6 +17,69 @@ function setupMPRIS() {
return player; return player;
} }
module.exports = { function registerMPRIS(win) {
setupMPRIS, const songControls = getSongControls(win);
}; const { playPause, next, previous } = songControls;
try {
const secToMicro = n => Math.round(Number(n) * 1e6);
const microToSec = n => Math.round(Number(n) / 1e6);
const seekTo = e => win.webContents.send("seekTo", microToSec(e.position));
const seekBy = o => win.webContents.send("seekBy", microToSec(o));
const player = setupMPRIS();
ipcMain.on('seeked', (_, t) => player.seeked(secToMicro(t)));
let currentSeconds = 0;
ipcMain.on('timeChanged', (_, t) => currentSeconds = t);
player.getPosition = () => secToMicro(currentSeconds)
player.on("raise", () => {
win.setSkipTaskbar(false);
win.show();
});
player.on("play", () => {
if (player.playbackStatus !== 'Playing') {
player.playbackStatus = 'Playing';
playPause()
}
});
player.on("pause", () => {
if (player.playbackStatus !== 'Paused') {
player.playbackStatus = 'Paused';
playPause()
}
});
player.on("playpause", playPause);
player.on("next", next);
player.on("previous", previous);
player.on('seek', seekBy);
player.on('position', seekTo);
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': '/'
};
if (songInfo.album) data['xesam:album'] = songInfo.album;
player.metadata = data;
player.seeked(secToMicro(songInfo.elapsedSeconds))
player.playbackStatus = songInfo.isPaused ? "Paused" : "Playing"
}
})
} catch (e) {
console.warn("Error in MPRIS", e);
}
}
module.exports = registerMPRIS;

View File

@ -0,0 +1,37 @@
const hark = require("hark/hark.bundle.js");
module.exports = () => {
let isSilent = false;
document.addEventListener("apiLoaded", () => {
const video = document.querySelector("video");
const speechEvents = hark(video, {
threshold: -100, // dB (-100 = absolute silence, 0 = loudest)
interval: 2, // ms
});
const skipSilence = () => {
if (isSilent && !video.paused) {
video.currentTime += 0.2; // in s
}
};
speechEvents.on("speaking", function () {
isSilent = false;
});
speechEvents.on("stopped_speaking", function () {
if (!(video.paused || video.seeking || video.ended)) {
isSilent = true;
skipSilence();
}
});
video.addEventListener("play", function () {
skipSilence();
});
video.addEventListener("seeked", function () {
skipSilence();
});
});
};

View File

@ -1,8 +1,8 @@
const fetch = require("node-fetch"); const fetch = require("node-fetch");
const is = require("electron-is"); const is = require("electron-is");
const { ipcMain } = require("electron");
const defaultConfig = require("../../config/defaults"); const defaultConfig = require("../../config/defaults");
const registerCallback = require("../../providers/song-info");
const { sortSegments } = require("./segments"); const { sortSegments } = require("./segments");
let videoID; let videoID;
@ -13,15 +13,10 @@ module.exports = (win, options) => {
...options, ...options,
}; };
registerCallback(async (info) => { ipcMain.on("video-src-changed", async (_, data) => {
const newURL = info.url || win.webContents.getURL(); videoID = JSON.parse(data)?.videoDetails?.videoId;
const newVideoID = new URL(newURL).searchParams.get("v"); const segments = await fetchSegments(apiURL, categories);
win.webContents.send("sponsorblock-skip", segments);
if (videoID !== newVideoID) {
videoID = newVideoID;
const segments = await fetchSegments(apiURL, categories);
win.webContents.send("sponsorblock-skip", segments);
}
}); });
}; };

View File

@ -1,33 +1,52 @@
const { ipcRenderer } = require("electron"); const { ipcMain } = require("electron");
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const registerCallback = require("../../providers/song-info"); const registerCallback = require("../../providers/song-info");
const post = (data) => { const secToMilisec = t => Math.round(Number(t) * 1e3);
const data = {
cover_url: '',
title: '',
artists: [],
status: '',
progress: 0,
duration: 0,
album_url: '',
album: undefined
};
const post = async (data) => {
const port = 1608; const port = 1608;
headers = {'Content-Type': 'application/json', headers = {
'Content-Type': 'application/json',
'Accept': 'application/json', 'Accept': 'application/json',
'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*'} 'Access-Control-Allow-Origin': '*'
}
const url = `http://localhost:${port}/`; const url = `http://localhost:${port}/`;
fetch(url, {method: 'POST', headers, body:JSON.stringify({data})}); fetch(url, { method: 'POST', headers, body: JSON.stringify({ data }) }).catch(e => console.log(`Error: '${e.code || e.errno}' - when trying to access obs-tuna webserver at port ${port}`));
} }
module.exports = async (win) => { module.exports = async (win) => {
registerCallback((songInfo) => { ipcMain.on('timeChanged', async (_, t) => {
if (!data.title) return;
data.progress = secToMilisec(t);
post(data);
});
// Register the callback registerCallback((songInfo) => {
if (songInfo.title.length === 0 && songInfo.artist.length === 0) { if (!songInfo.title && !songInfo.artist) {
return; return;
} }
const duration = Number(songInfo.songDuration)*1000 data.duration = secToMilisec(songInfo.songDuration)
const progress = Number(songInfo.elapsedSeconds)*1000 data.progress = secToMilisec(songInfo.elapsedSeconds)
const cover_url = songInfo.imageSrc data.cover_url = songInfo.imageSrc;
const album_url = songInfo.imageSrc data.album_url = songInfo.imageSrc;
const title = songInfo.title data.title = songInfo.title;
const artists = [songInfo.artist] data.artists = [songInfo.artist];
const status = !songInfo.isPaused ? 'Playing': 'Paused' data.status = songInfo.isPaused ? 'stopped' : 'playing';
post({ cover_url, title, artists, status, progress, duration, album_url}); data.album = songInfo.album;
post(data);
}) })
} }

View File

@ -42,15 +42,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

@ -1,31 +1,51 @@
const { ElementFromFile, templatePath } = require("../utils"); const { ElementFromFile, templatePath } = require("../utils");
const { setOptions } = require("../../config/plugins"); const { setOptions, isEnabled } = require("../../config/plugins");
const moveVolumeHud = isEnabled("precise-volume") ? require("../precise-volume/front").moveVolumeHud : ()=>{};
function $(selector) { return document.querySelector(selector); } function $(selector) { return document.querySelector(selector); }
let options; let options, player, video, api;
const switchButtonDiv = ElementFromFile( const switchButtonDiv = ElementFromFile(
templatePath(__dirname, "button_template.html") templatePath(__dirname, "button_template.html")
); );
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) {
api = e.detail;
player = $('ytmusic-player');
video = $('video');
function setup() {
$('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);
forcePlaybackMode(); forcePlaybackMode();
// fix black video
video.style.height = "auto";
} }
// button checked = show video // button checked = show video
@ -35,51 +55,73 @@ function setup() {
setOptions("video-toggle", options); setOptions("video-toggle", options);
}) })
$('video').addEventListener('loadedmetadata', videoStarted); video.addEventListener('srcChanged', videoStarted);
observeThumbnail();
} }
function changeDisplay(showVideo) { function changeDisplay(showVideo) {
if (!showVideo && $('ytmusic-player').getAttribute('playback-mode') !== "ATV_PREFERRED") { player.style.margin = showVideo ? '' : 'auto 0px';
$('video').style.top = "0"; player.setAttribute('playback-mode', showVideo ? 'OMV_PREFERRED' : 'ATV_PREFERRED');
$('ytmusic-player').style.margin = "auto 21.5px";
$('ytmusic-player').setAttribute('playback-mode', "ATV_PREFERRED");
}
showVideo ? $('#song-video.ytmusic-player').style.display = showVideo ? 'block' : 'none';
$('#song-video.ytmusic-player').style.display = "unset" : $('#song-image').style.display = showVideo ? 'none' : 'block';
$('#song-video.ytmusic-player').style.display = "none";
if (showVideo && !video.style.top) {
video.style.top = `${(player.clientHeight - video.clientHeight) / 2}px`;
}
moveVolumeHud(showVideo);
} }
function videoStarted() { function videoStarted() {
if (videoExist()) { if (api.getPlayerResponse().videoDetails.musicVideoType !== 'MUSIC_VIDEO_TYPE_ATV') {
const thumbnails = $('#movie_player').getPlayerResponse()?.videoDetails?.thumbnail?.thumbnails; // switch to high res thumbnail
if (thumbnails && thumbnails.length > 0) { forceThumbnail($('#song-image img'));
$('#song-image img').src = thumbnails[thumbnails.length-1].url; // show toggle button
}
switchButtonDiv.style.display = "initial"; switchButtonDiv.style.display = "initial";
// change display to video mode if video exist & video is hidden & option.hideVideo = false
if (!options.hideVideo && $('#song-video.ytmusic-player').style.display === "none") { if (!options.hideVideo && $('#song-video.ytmusic-player').style.display === "none") {
changeDisplay(true); changeDisplay(true);
} else {
moveVolumeHud(!options.hideVideo);
} }
} else { } else {
// video doesn't exist -> switch to song mode
changeDisplay(false); changeDisplay(false);
// hide toggle button
switchButtonDiv.style.display = "none"; switchButtonDiv.style.display = "none";
} }
} }
function videoExist() {
return $('#player').videoMode_;
}
// on load, after a delay, the page overrides the playback-mode to 'OMV_PREFERRED' which causes weird aspect ratio in the image container // on load, after a delay, the page overrides the playback-mode to 'OMV_PREFERRED' which causes weird aspect ratio in the image container
// this function fix the problem by overriding that override :) // this function fix the problem by overriding that override :)
function forcePlaybackMode() { function forcePlaybackMode() {
const playbackModeObserver = new MutationObserver(mutations => { const playbackModeObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => { mutations.forEach(mutation => {
if (mutation.type === 'attributes' && mutation.attributeName === 'playback-mode' && mutation.target.getAttribute('playback-mode') !== "ATV_PREFERRED") { if (mutation.target.getAttribute('playback-mode') !== "ATV_PREFERRED") {
playbackModeObserver.disconnect(); playbackModeObserver.disconnect();
mutation.target.setAttribute('playback-mode', "ATV_PREFERRED"); mutation.target.setAttribute('playback-mode', "ATV_PREFERRED");
} }
}); });
}); });
playbackModeObserver.observe($('ytmusic-player'), { attributeFilter: ["playback-mode"] }) playbackModeObserver.observe(player, { attributeFilter: ["playback-mode"] });
}
function observeThumbnail() {
const playbackModeObserver = new MutationObserver(mutations => {
if (!player.videoMode_) return;
mutations.forEach(mutation => {
if (!mutation.target.src.startsWith('data:')) return;
forceThumbnail(mutation.target)
});
});
playbackModeObserver.observe($('#song-image img'), { attributeFilter: ["src"] })
}
function forceThumbnail(img) {
const thumbnails = $('#movie_player').getPlayerResponse()?.videoDetails?.thumbnail?.thumbnails;
if (thumbnails && thumbnails.length > 0) {
img.src = thumbnails[thumbnails.length - 1].url.split("?")[0];
}
} }

View File

@ -1,13 +1,45 @@
const { setOptions } = 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",
checked: options.forceHide, checked: options.forceHide,
click: item => { click: item => {
options.forceHide = item.checked; options.forceHide = item.checked;
setOptions("video-toggle", options); setMenuOptions("video-toggle", options);
} }
} }
]; ];

View File

@ -1,11 +1,12 @@
const path = require("path"); const path = require("path");
const { remote } = require("electron"); 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 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 plugins = config.plugins.getEnabled(); const plugins = config.plugins.getEnabled();
@ -45,6 +46,9 @@ document.addEventListener("DOMContentLoaded", () => {
// inject song-info provider // inject song-info provider
setupSongInfo(); setupSongInfo();
// inject song-controls
setupSongControls();
// inject front logger // inject front logger
setupFrontLogger(); setupFrontLogger();
@ -79,9 +83,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

@ -0,0 +1,6 @@
const app = require("electron").app || require('@electron/remote').app;
module.exports.restart = () => {
app.relaunch();
app.exit();
};

View File

@ -1,8 +1,8 @@
const customTitlebar = require("custom-electron-titlebar"); const { Titlebar, Color } = require("custom-electron-titlebar");
module.exports = () => { module.exports = () => {
new customTitlebar.Titlebar({ new Titlebar({
backgroundColor: customTitlebar.Color.fromHex("#050505"), backgroundColor: Color.fromHex("#050505"),
minimizable: false, minimizable: false,
maximizable: false, maximizable: false,
menu: null menu: null

View File

@ -1,18 +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,
enableRemoteModule: true } : {
customStylesheet: "dark",
icon: iconPath
}; };
module.exports = () => promptOptions; module.exports = () => promptOptions;

View File

@ -0,0 +1,13 @@
const { ipcRenderer } = require("electron");
const config = require("../config");
const is = require("electron-is");
module.exports.setupSongControls = () => {
document.addEventListener('apiLoaded', e => {
ipcRenderer.on("seekTo", (_, t) => e.detail.seekTo(t));
ipcRenderer.on("seekBy", (_, t) => e.detail.seekBy(t));
if (is.linux() && config.plugins.isEnabled('shortcuts')) { // MPRIS Enabled
document.querySelector('video').addEventListener('seeked', v => ipcRenderer.send('seeked', v.target.currentTime));
}
}, { once: true, passive: true })
};

View File

@ -13,8 +13,8 @@ module.exports = (win) => {
previous: () => pressKey(win, "k"), previous: () => pressKey(win, "k"),
next: () => pressKey(win, "j"), next: () => pressKey(win, "j"),
playPause: () => pressKey(win, "space"), playPause: () => pressKey(win, "space"),
like: () => pressKey(win, "_"), like: () => pressKey(win, "+"),
dislike: () => pressKey(win, "+"), dislike: () => pressKey(win, "_"),
go10sBack: () => pressKey(win, "h"), go10sBack: () => pressKey(win, "h"),
go10sForward: () => pressKey(win, "l"), go10sForward: () => pressKey(win, "l"),
go1sBack: () => pressKey(win, "h", ["shift"]), go1sBack: () => pressKey(win, "h", ["shift"]),
@ -24,8 +24,6 @@ module.exports = (win) => {
// General // General
volumeMinus10: () => pressKey(win, "-"), volumeMinus10: () => pressKey(win, "-"),
volumePlus10: () => pressKey(win, "="), volumePlus10: () => pressKey(win, "="),
dislikeAndNext: () => pressKey(win, "-", ["shift"]),
like: () => pressKey(win, "=", ["shift"]),
fullscreen: () => pressKey(win, "f"), fullscreen: () => pressKey(win, "f"),
muteUnmute: () => pressKey(win, "m"), muteUnmute: () => pressKey(win, "m"),
maximizeMinimisePlayer: () => pressKey(win, "q"), maximizeMinimisePlayer: () => pressKey(win, "q"),
@ -38,14 +36,14 @@ module.exports = (win) => {
pressKey(win, "g"); pressKey(win, "g");
pressKey(win, "l"); pressKey(win, "l");
}, },
goToHotlist: () => {
pressKey(win, "g");
pressKey(win, "t");
},
goToSettings: () => { goToSettings: () => {
pressKey(win, "g"); pressKey(win, "g");
pressKey(win, ","); pressKey(win, ",");
}, },
goToExplore: () => {
pressKey(win, "g");
pressKey(win, "e");
},
search: () => pressKey(win, "/"), search: () => pressKey(win, "/"),
showShortcuts: () => pressKey(win, "/", ["shift"]), showShortcuts: () => pressKey(win, "/", ["shift"]),
}; };

View File

@ -1,19 +1,65 @@
const { ipcRenderer } = require("electron"); const { ipcRenderer } = require("electron");
const is = require('electron-is');
const { getImage } = require("./song-info"); const { getImage } = require("./song-info");
const config = require("../config");
global.songInfo = {}; global.songInfo = {};
const $ = s => document.querySelector(s);
const $$ = s => Array.from(document.querySelectorAll(s));
ipcRenderer.on("update-song-info", async (_, extractedSongInfo) => { ipcRenderer.on("update-song-info", async (_, extractedSongInfo) => {
global.songInfo = JSON.parse(extractedSongInfo); global.songInfo = JSON.parse(extractedSongInfo);
global.songInfo.image = await getImage(global.songInfo.imageSrc); global.songInfo.image = await getImage(global.songInfo.imageSrc);
}); });
// used because 'loadeddata' or 'loadedmetadata' weren't firing on song start for some users (https://github.com/th-ch/youtube-music/issues/473)
const srcChangedEvent = new CustomEvent('srcChanged');
module.exports = () => { module.exports = () => {
document.addEventListener('apiLoaded', e => { document.addEventListener('apiLoaded', apiEvent => {
document.querySelector('video').addEventListener('loadedmetadata', () => { if (config.plugins.isEnabled('tuna-obs') ||
const data = e.detail.getPlayerResponse(); (is.linux() && config.plugins.isEnabled('shortcuts'))) {
ipcRenderer.send("song-info-request", JSON.stringify(data)); setupTimeChangeListener();
}); }
}, { once: true, passive: true }) const video = $('video');
// name = "dataloaded" and abit later "dataupdated"
apiEvent.detail.addEventListener('videodatachange', (name, _dataEvent) => {
if (name !== 'dataloaded') return;
video.dispatchEvent(srcChangedEvent);
sendSongInfo();
})
for (const status of ['playing', 'pause']) {
video.addEventListener(status, e => {
if (Math.round(e.target.currentTime) > 0) {
ipcRenderer.send("playPaused", {
isPaused: status === 'pause',
elapsedSeconds: Math.floor(e.target.currentTime)
});
}
});
}
function sendSongInfo() {
const data = apiEvent.detail.getPlayerResponse();
data.videoDetails.album = $$(
".byline.ytmusic-player-bar > .yt-simple-endpoint"
).find(e => e.href?.includes("browse"))?.textContent;
data.videoDetails.elapsedSeconds = Math.floor(video.currentTime);
data.videoDetails.isPaused = false;
ipcRenderer.send("video-src-changed", JSON.stringify(data));
}
}, { once: true, passive: true });
}; };
function setupTimeChangeListener() {
const progressObserver = new MutationObserver(mutations => {
ipcRenderer.send('timeChanged', mutations[0].target.value);
global.songInfo.elapsedSeconds = mutations[0].target.value;
});
progressObserver.observe($('#progress-bar'), { attributeFilter: ["value"] })
}

View File

@ -4,32 +4,6 @@ const fetch = require("node-fetch");
const config = require("../config"); const config = require("../config");
// Grab the progress using the selector
const getProgress = async (win) => {
// Get current value of the progressbar element
return win.webContents.executeJavaScript(
'document.querySelector("#progress-bar").value'
);
};
// Grab the native image using the src
const getImage = async (src) => {
const result = await fetch(src);
const buffer = await result.buffer();
const output = nativeImage.createFromBuffer(buffer);
if (output.isEmpty() && !src.endsWith(".jpg") && src.includes(".jpg")) { // fix hidden webp files (https://github.com/th-ch/youtube-music/issues/315)
return getImage(src.slice(0, src.lastIndexOf(".jpg")+4));
} else {
return output;
}
};
// To find the paused status, we check if the title contains `-`
const getPausedStatus = async (win) => {
const title = await win.webContents.executeJavaScript("document.title");
return !title.includes("-");
};
// Fill songInfo with empty values // Fill songInfo with empty values
/** /**
* @typedef {songInfo} SongInfo * @typedef {songInfo} SongInfo
@ -45,23 +19,55 @@ const songInfo = {
songDuration: 0, songDuration: 0,
elapsedSeconds: 0, elapsedSeconds: 0,
url: "", url: "",
album: undefined,
videoId: "",
playlistId: "",
};
// Grab the native image using the src
const getImage = async (src) => {
const result = await fetch(src);
const buffer = await result.buffer();
const output = nativeImage.createFromBuffer(buffer);
if (output.isEmpty() && !src.endsWith(".jpg") && src.includes(".jpg")) { // fix hidden webp files (https://github.com/th-ch/youtube-music/issues/315)
return getImage(src.slice(0, src.lastIndexOf(".jpg") + 4));
} else {
return output;
}
}; };
const handleData = async (responseText, win) => { const handleData = async (responseText, win) => {
let data = JSON.parse(responseText); const data = JSON.parse(responseText);
songInfo.title = cleanupName(data?.videoDetails?.title); if (!data) return;
songInfo.artist =cleanupName(data?.videoDetails?.author);
songInfo.views = data?.videoDetails?.viewCount;
songInfo.imageSrc = data?.videoDetails?.thumbnail?.thumbnails?.pop()?.url;
songInfo.songDuration = data?.videoDetails?.lengthSeconds;
songInfo.image = await getImage(songInfo.imageSrc);
songInfo.uploadDate = data?.microformat?.microformatDataRenderer?.uploadDate;
songInfo.url = data?.microformat?.microformatDataRenderer?.urlCanonical?.split("&")[0];
// used for options.resumeOnStart const microformat = data.microformat?.microformatDataRenderer;
config.set("url", data?.microformat?.microformatDataRenderer?.urlCanonical); if (microformat) {
songInfo.uploadDate = microformat.uploadDate;
songInfo.url = microformat.urlCanonical?.split("&")[0];
songInfo.playlistId = new URL(microformat.urlCanonical).searchParams.get("list");
// used for options.resumeOnStart
config.set("url", microformat.urlCanonical);
}
win.webContents.send("update-song-info", JSON.stringify(songInfo)); const videoDetails = data.videoDetails;
if (videoDetails) {
songInfo.title = cleanupName(videoDetails.title);
songInfo.artist = cleanupName(videoDetails.author);
songInfo.views = videoDetails.viewCount;
songInfo.songDuration = videoDetails.lengthSeconds;
songInfo.elapsedSeconds = videoDetails.elapsedSeconds;
songInfo.isPaused = videoDetails.isPaused;
songInfo.videoId = videoDetails.videoId;
songInfo.album = data?.videoDetails?.album; // Will be undefined if video exist
const oldUrl = songInfo.imageSrc;
songInfo.imageSrc = videoDetails.thumbnail?.thumbnails?.pop()?.url.split("?")[0];
if (oldUrl !== songInfo.imageSrc) {
songInfo.image = await getImage(songInfo.imageSrc);
}
win.webContents.send("update-song-info", JSON.stringify(songInfo));
}
}; };
// This variable will be filled with the callbacks once they register // This variable will be filled with the callbacks once they register
@ -80,41 +86,39 @@ const registerCallback = (callback) => {
callbacks.push(callback); callbacks.push(callback);
}; };
let handlingData = false;
const registerProvider = (win) => { const registerProvider = (win) => {
win.on("page-title-updated", async () => {
// Get and set the new data
songInfo.isPaused = await getPausedStatus(win);
const elapsedSeconds = await getProgress(win);
songInfo.elapsedSeconds = elapsedSeconds;
// Trigger the callbacks
callbacks.forEach((c) => {
c(songInfo);
});
});
// This will be called when the song-info-front finds a new request with song data // This will be called when the song-info-front finds a new request with song data
ipcMain.on("song-info-request", async (_, responseText) => { ipcMain.on("video-src-changed", async (_, responseText) => {
handlingData = true;
await handleData(responseText, win); await handleData(responseText, win);
handlingData = false;
callbacks.forEach((c) => { callbacks.forEach((c) => {
c(songInfo); c(songInfo);
}); });
}); });
ipcMain.on("playPaused", (_, { isPaused, elapsedSeconds }) => {
songInfo.isPaused = isPaused;
songInfo.elapsedSeconds = elapsedSeconds;
if (handlingData) return;
callbacks.forEach((c) => {
c(songInfo);
});
})
}; };
const suffixesToRemove = [ const suffixesToRemove = [
" - topic", " - topic",
"vevo", "vevo",
" (performance video)", " (performance video)",
" (official music video)",
" (official video)",
" (clip officiel)", " (clip officiel)",
]; ];
function cleanupName(name) { function cleanupName(name) {
if (!name) return name; if (!name) return name;
const lowCaseName = name.toLowerCase(); name = name.replace(/\((?:official)?[ ]?(?:music)?[ ]?(?:lyric[s]?)?[ ]?(?:video)?\)$/i, '')
const lowCaseName = name.toLowerCase();
for (const suffix of suffixesToRemove) { for (const suffix of suffixesToRemove) {
if (lowCaseName.endsWith(suffix)) { if (lowCaseName.endsWith(suffix)) {
return name.slice(0, -suffix.length); return name.slice(0, -suffix.length);

View File

@ -34,25 +34,57 @@ 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).
## Available plugins: ## Available plugins:
- **Ad Blocker**: block all ads and tracking out of the box
- **Audio compressor**: apply compression to audio (lowers the volume of the loudest parts of the signal and raises the volume of the softest parts) - **Ad Blocker**: Block all ads and tracking out of the box
- **Blur navigation bar**: makes navigation bar transparent and blurry
- **Disable autoplay**: makes every song start in "paused" mode - **Audio Compressor**: Apply compression to audio (lowers the volume of the loudest parts of the signal and raises the volume of the softest parts)
- [**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)
- **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
- [**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)
- **Downloader**: downloads MP3 [directly from the interface](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl) - **Downloader**: downloads MP3 [directly from the interface](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
- **In-app menu**: [gives bars a fancy, dark look](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
- **Exponential Volume**: Makes the volume slider [exponential](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) so it's easier to select lower volumes.
- **In-App Menu**: [gives bars a fancy, dark look](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
> (see [this post](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) if you have problem accessing the menu after enabling this plugin and hide-menu option) > (see [this post](https://github.com/th-ch/youtube-music/issues/410#issuecomment-952060709) if you have problem accessing the menu after enabling this plugin and hide-menu option)
- [**Last.fm**](https://www.last.fm/): scrobbles support
- **Navigation**: next/back navigation arrows directly integrated in the interface, like in your favorite browser - [**Last.fm**](https://www.last.fm/): Scrobbles support
- **No Google Login**: remove Google login buttons and links from the interface
- **Notifications**: display a notification when a song starts playing ([interactive notifications](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) are available on windows) - **Lyrics Genius**: Adds lyrics support for most songs
- **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**: customizable volume steps for more comfort, allows controlling the volume precisely using mousewheel - **Navigation**: Next/Back navigation arrows directly integrated in the interface, like in your favorite browser
- **Quality changer**: Allows changing the video quality with a [button](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) on the video overlay
- **No Google Login**: Remove Google login buttons and links from the interface
- **Notifications**: Display a notification when a song starts playing ([interactive notifications](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) are available on windows)
- **Picture in picture**: allows to switch the app to picture-in-picture mode
- **Playback Speed**: Listen fast, listen slow! [Adds a slider that controls song speed](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
- **Precise Volume**: Control the volume precisely using mousewheel/hotkeys, with a custom hud and customizable volume steps
- **Quality Changer**: Allows changing the video quality with a [button](https://user-images.githubusercontent.com/78568641/138574366-70324a5e-2d64-4f6a-acdd-dc2a2b9cecc5.png) on the video overlay
- **Shortcuts**: Allows setting global hotkeys for playback (play/pause/next/previous) + disable [media osd](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png) by overriding media keys + enable Ctrl/CMD + F to search + enable linux mpris support for mediakeys + [custom hotkeys](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50) for [advanced users](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902) - **Shortcuts**: Allows setting global hotkeys for playback (play/pause/next/previous) + disable [media osd](https://user-images.githubusercontent.com/84923831/128601225-afa38c1f-dea8-4209-9f72-0f84c1dd8b54.png) by overriding media keys + enable Ctrl/CMD + F to search + enable linux mpris support for mediakeys + [custom hotkeys](https://github.com/Araxeus/youtube-music/blob/1e591d6a3df98449bcda6e63baab249b28026148/providers/song-controls.js#L13-L50) for [advanced users](https://github.com/th-ch/youtube-music/issues/106#issuecomment-952156902)
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): skips non-music parts
- **Taskbar media control**: control playback from your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png) - **Skip-Silences** - Automatically skip silenced sections
- **Touchbar**: custom TouchBar layout for macOS
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): Automatically Skips non-music parts like intro/outro or parts of music videos where the song isn't playing
- **Taskbar Media Control**: Control playback from your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
- **Touchbar**: Custom TouchBar layout for macOS
- **Tuna-OBS**: Integration with [OBS](https://obsproject.com/)'s plugin [Tuna](https://obsproject.com/forum/resources/tuna.843/)
- **Video Toggle**: Adds a button to switch between Video/Song mode. can also optionally remove the whole video tab - **Video Toggle**: Adds a button to switch between Video/Song mode. can also optionally remove the whole video tab
--- ---
@ -138,7 +170,7 @@ Builds the app for macOS, Linux, and Windows, using [electron-builder](https://g
yarn test yarn test
``` ```
Uses [Spectron](https://www.electronjs.org/spectron) to test the app. Uses [Playwright](https://playwright.dev/) to test the app.
## License ## License

View File

@ -1,9 +1,7 @@
const path = require("path"); const path = require("path");
const getPort = require("get-port");
const NodeEnvironment = require("jest-environment-node"); const NodeEnvironment = require("jest-environment-node");
const electronPath = require("electron"); const { _electron: electron } = require("playwright");
const { Application } = require("spectron");
class TestEnvironment extends NodeEnvironment { class TestEnvironment extends NodeEnvironment {
constructor(config) { constructor(config) {
@ -14,21 +12,12 @@ class TestEnvironment extends NodeEnvironment {
await super.setup(); await super.setup();
const appPath = path.resolve(__dirname, ".."); const appPath = path.resolve(__dirname, "..");
const port = await getPort(); this.global.__APP__ = await electron.launch({ args: [appPath] });
this.global.__APP__ = new Application({
path: electronPath,
args: [appPath],
port,
});
await this.global.__APP__.start();
const { client } = this.global.__APP__;
await client.waitUntilWindowLoaded();
} }
async teardown() { async teardown() {
if (this.global.__APP__.isRunning()) { if (this.global.__APP__) {
await this.global.__APP__.stop(); await this.global.__APP__.close();
} }
await super.teardown(); await super.teardown();
} }

View File

@ -6,22 +6,11 @@ describe("YouTube Music App", () => {
const app = global.__APP__; const app = global.__APP__;
test("With default settings, app is launched and visible", async () => { test("With default settings, app is launched and visible", async () => {
expect(app.isRunning()).toBe(true); const window = await app.firstWindow();
const title = await window.title();
const win = app.browserWindow;
const isMenuVisible = await win.isMenuBarVisible();
expect(isMenuVisible).toBe(true);
const isVisible = await win.isVisible();
expect(isVisible).toBe(true);
const { width, height } = await win.getBounds();
expect(width).toBeGreaterThan(0);
expect(height).toBeGreaterThan(0);
const { client } = app;
const title = await client.getTitle();
expect(title).toEqual("YouTube Music"); expect(title).toEqual("YouTube Music");
const url = window.url();
expect(url.startsWith("https://music.youtube.com")).toBe(true);
}); });
}); });

13
tray.js
View File

@ -1,6 +1,6 @@
const path = require("path"); const path = require("path");
const { Menu, nativeImage, Tray } = require("electron"); const { app, Menu, nativeImage, Tray } = require("electron");
const config = require("./config"); const config = require("./config");
const getSongControls = require("./providers/song-controls"); const getSongControls = require("./providers/song-controls");
@ -27,7 +27,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,6 +60,7 @@ module.exports.setUpTray = (app, win) => {
label: "Show", label: "Show",
click: () => { click: () => {
win.show(); win.show();
app.dock?.show();
}, },
}, },
{ {
@ -63,7 +70,7 @@ module.exports.setUpTray = (app, win) => {
app.quit(); app.quit();
}, },
}, },
{ role: "quit" } { role: "quit" },
]; ];
const trayMenu = Menu.buildFromTemplate(template); const trayMenu = Menu.buildFromTemplate(template);

View File

@ -0,0 +1,30 @@
From c5faeceaf36f4b9fb27e5269990b716a25ecbe43 Mon Sep 17 00:00:00 2001
From: Jake Barnes <me@jakebarn.es>
Date: Mon, 3 May 2021 11:58:27 +1000
Subject: [PATCH] Fix activation not writing to pipe
---
src/toasteventhandler.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/toasteventhandler.cpp b/src/toasteventhandler.cpp
index d45d92f..e239dde 100644
--- a/src/toasteventhandler.cpp
+++ b/src/toasteventhandler.cpp
@@ -79,6 +79,13 @@ IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification * /*sender*/,
std::wcout << dataMap.at(L"button") << std::endl;
m_userAction = SnoreToastActions::Actions::ButtonClicked;
}
+ if (!m_toast.pipeName().empty()) {
+ if (m_userAction == SnoreToastActions::Actions::ButtonClicked) {
+ Utils::writePipe(m_toast.pipeName(), m_toast.formatAction(m_userAction, { { L"button", dataMap.at(L"button") } }));
+ } else {
+ Utils::writePipe(m_toast.pipeName(), m_toast.formatAction(m_userAction));
+ }
+ }
}
SetEvent(m_event);
return S_OK;
--
2.35.1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 816 KiB

After

Width:  |  Height:  |  Size: 726 KiB

1530
yarn.lock

File diff suppressed because it is too large Load Diff

View File

@ -34,3 +34,8 @@ img {
-webkit-user-select: none; -webkit-user-select: none;
user-select: none; user-select: none;
} }
/* Hide cast button which doesn't work */
ytmusic-cast-button {
display: none !important;
}