From d2265b59d78143cf51fe4dc3d5dee9da66873cc1 Mon Sep 17 00:00:00 2001 From: TC Date: Thu, 7 Apr 2022 20:01:29 +0200 Subject: [PATCH 01/13] Create first version of picture in picture plugin --- plugins/picture-in-picture/back.js | 58 +++++++++++++++++++ plugins/picture-in-picture/front.js | 42 ++++++++++++++ plugins/picture-in-picture/style.css | 7 +++ .../templates/picture-in-picture.html | 51 ++++++++++++++++ 4 files changed, 158 insertions(+) create mode 100644 plugins/picture-in-picture/back.js create mode 100644 plugins/picture-in-picture/front.js create mode 100644 plugins/picture-in-picture/style.css create mode 100644 plugins/picture-in-picture/templates/picture-in-picture.html diff --git a/plugins/picture-in-picture/back.js b/plugins/picture-in-picture/back.js new file mode 100644 index 00000000..02ade2b9 --- /dev/null +++ b/plugins/picture-in-picture/back.js @@ -0,0 +1,58 @@ +const path = require("path"); + +const { app, ipcMain } = require("electron"); + +const { injectCSS } = require("../utils"); + +let isInPiPMode = false; +let originalPosition; +let originalSize; + +const pipPosition = [10, 10]; +const pipSize = [400, 220]; + +const togglePiP = async (win) => { + isInPiPMode = !isInPiPMode; + + if (isInPiPMode) { + injectCSS(win.webContents, path.join(__dirname, "style.css")); + + originalPosition = win.getPosition(); + originalSize = win.getSize(); + + win.setFullScreenable(false); + await win.webContents.executeJavaScript( + // Go fullscreen + `document.querySelector(".fullscreen-button").click()` + ); + + app.dock.hide(); + win.setVisibleOnAllWorkspaces(true, { + visibleOnFullScreen: true, + }); + app.dock.show(); + win.setAlwaysOnTop(true, "screen-saver", 1); + } else { + win.setFullScreenable(true); + await win.webContents.executeJavaScript( + // Exit fullscreen + `document.querySelector(".exit-fullscreen-button").click()` + ); + + 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) => { + ipcMain.on("picture-in-picture", async () => { + await togglePiP(win); + }); +}; diff --git a/plugins/picture-in-picture/front.js b/plugins/picture-in-picture/front.js new file mode 100644 index 00000000..637c7fb6 --- /dev/null +++ b/plugins/picture-in-picture/front.js @@ -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; diff --git a/plugins/picture-in-picture/style.css b/plugins/picture-in-picture/style.css new file mode 100644 index 00000000..de6a9ace --- /dev/null +++ b/plugins/picture-in-picture/style.css @@ -0,0 +1,7 @@ +/* Make entire window draggable */ +body { + -webkit-app-region: drag; +} +button { + -webkit-app-region: no-drag; +} diff --git a/plugins/picture-in-picture/templates/picture-in-picture.html b/plugins/picture-in-picture/templates/picture-in-picture.html new file mode 100644 index 00000000..6dd0440c --- /dev/null +++ b/plugins/picture-in-picture/templates/picture-in-picture.html @@ -0,0 +1,51 @@ + From 4ddd2f339b0491b9793b0e3dc98935b64f2b7496 Mon Sep 17 00:00:00 2001 From: TC Date: Thu, 7 Apr 2022 20:05:13 +0200 Subject: [PATCH 02/13] add PiP plugin to readme list --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index 2700d9f8..10b6a279 100644 --- a/readme.md +++ b/readme.md @@ -63,6 +63,8 @@ Install the `youtube-music-bin` package from the AUR. For AUR installation instr - **Notifications**: Display a notification when a song starts playing ([interactive notifications](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) are available on windows) +- **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 From 6d5fe9561ef8ec058a27244907893bb99d58b0f3 Mon Sep 17 00:00:00 2001 From: th-ch Date: Fri, 8 Apr 2022 15:43:57 +0200 Subject: [PATCH 03/13] Update plugins/picture-in-picture/back.js Co-authored-by: Araxeus --- plugins/picture-in-picture/back.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/picture-in-picture/back.js b/plugins/picture-in-picture/back.js index 02ade2b9..a8836753 100644 --- a/plugins/picture-in-picture/back.js +++ b/plugins/picture-in-picture/back.js @@ -26,7 +26,7 @@ const togglePiP = async (win) => { `document.querySelector(".fullscreen-button").click()` ); - app.dock.hide(); + app.dock?.hide(); win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true, }); From 57290c4164cdcc34b1c17c147538ed79af27346a Mon Sep 17 00:00:00 2001 From: th-ch Date: Fri, 8 Apr 2022 15:44:03 +0200 Subject: [PATCH 04/13] Update plugins/picture-in-picture/back.js Co-authored-by: Araxeus --- plugins/picture-in-picture/back.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/picture-in-picture/back.js b/plugins/picture-in-picture/back.js index a8836753..0f9ab1a0 100644 --- a/plugins/picture-in-picture/back.js +++ b/plugins/picture-in-picture/back.js @@ -30,7 +30,7 @@ const togglePiP = async (win) => { win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true, }); - app.dock.show(); + app.dock?.show(); win.setAlwaysOnTop(true, "screen-saver", 1); } else { win.setFullScreenable(true); From 742a9496802a5b01f0ca7573eebf9c2eb1b5db4a Mon Sep 17 00:00:00 2001 From: th-ch Date: Fri, 8 Apr 2022 15:45:33 +0200 Subject: [PATCH 05/13] Update plugins/picture-in-picture/back.js Co-authored-by: Araxeus --- plugins/picture-in-picture/back.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/picture-in-picture/back.js b/plugins/picture-in-picture/back.js index 0f9ab1a0..63391527 100644 --- a/plugins/picture-in-picture/back.js +++ b/plugins/picture-in-picture/back.js @@ -48,7 +48,7 @@ const togglePiP = async (win) => { win.setPosition(x, y); win.setSize(w, h); - win.setWindowButtonVisibility(!isInPiPMode); + win.setWindowButtonVisibility?.(!isInPiPMode); }; module.exports = (win) => { From 296ecb674093f67757125e5613acddf433b40c15 Mon Sep 17 00:00:00 2001 From: TC Date: Sat, 9 Apr 2022 12:10:22 +0200 Subject: [PATCH 06/13] Always inject style --- plugins/picture-in-picture/back.js | 3 +-- plugins/picture-in-picture/style.css | 6 ++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/picture-in-picture/back.js b/plugins/picture-in-picture/back.js index 63391527..2ece49e3 100644 --- a/plugins/picture-in-picture/back.js +++ b/plugins/picture-in-picture/back.js @@ -15,8 +15,6 @@ const togglePiP = async (win) => { isInPiPMode = !isInPiPMode; if (isInPiPMode) { - injectCSS(win.webContents, path.join(__dirname, "style.css")); - originalPosition = win.getPosition(); originalSize = win.getSize(); @@ -52,6 +50,7 @@ const togglePiP = async (win) => { }; module.exports = (win) => { + injectCSS(win.webContents, path.join(__dirname, "style.css")); ipcMain.on("picture-in-picture", async () => { await togglePiP(win); }); diff --git a/plugins/picture-in-picture/style.css b/plugins/picture-in-picture/style.css index de6a9ace..d944e4cc 100644 --- a/plugins/picture-in-picture/style.css +++ b/plugins/picture-in-picture/style.css @@ -5,3 +5,9 @@ body { button { -webkit-app-region: no-drag; } + +ytmusic-player-bar.pip svg, +ytmusic-player-bar.pip yt-formatted-string { + filter: drop-shadow(2px 4px 6px black); + color: white; +} From f3641f5072cb9ecfd8bc7b31cdaf05f6d96849d5 Mon Sep 17 00:00:00 2001 From: TC Date: Sat, 9 Apr 2022 12:11:05 +0200 Subject: [PATCH 07/13] Set in config when PiP mode is enabled --- plugins/picture-in-picture/back.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/picture-in-picture/back.js b/plugins/picture-in-picture/back.js index 2ece49e3..6af67b7a 100644 --- a/plugins/picture-in-picture/back.js +++ b/plugins/picture-in-picture/back.js @@ -2,6 +2,7 @@ const path = require("path"); const { app, ipcMain } = require("electron"); +const { setOptions } = require("../../config/plugins"); const { injectCSS } = require("../utils"); let isInPiPMode = false; @@ -13,6 +14,7 @@ const pipSize = [400, 220]; const togglePiP = async (win) => { isInPiPMode = !isInPiPMode; + setOptions("picture-in-picture", { isInPiP: isInPiPMode }); if (isInPiPMode) { originalPosition = win.getPosition(); From e58a580b2b25d4b592b883c36f44a17e8d483c59 Mon Sep 17 00:00:00 2001 From: TC Date: Sat, 9 Apr 2022 12:13:33 +0200 Subject: [PATCH 08/13] Restore fullscreenable after switching to PiP mode --- plugins/picture-in-picture/back.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/picture-in-picture/back.js b/plugins/picture-in-picture/back.js index 6af67b7a..c95dd209 100644 --- a/plugins/picture-in-picture/back.js +++ b/plugins/picture-in-picture/back.js @@ -25,6 +25,7 @@ const togglePiP = async (win) => { // Go fullscreen `document.querySelector(".fullscreen-button").click()` ); + win.setFullScreenable(true); app.dock?.hide(); win.setVisibleOnAllWorkspaces(true, { @@ -33,7 +34,6 @@ const togglePiP = async (win) => { app.dock?.show(); win.setAlwaysOnTop(true, "screen-saver", 1); } else { - win.setFullScreenable(true); await win.webContents.executeJavaScript( // Exit fullscreen `document.querySelector(".exit-fullscreen-button").click()` From 5ca0c6b8a96ad23121e2a79510674028ca8e7d72 Mon Sep 17 00:00:00 2001 From: TC Date: Sat, 9 Apr 2022 12:14:42 +0200 Subject: [PATCH 09/13] Ensure player is open when going PiP + add class --- plugins/picture-in-picture/back.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/plugins/picture-in-picture/back.js b/plugins/picture-in-picture/back.js index c95dd209..5effa79c 100644 --- a/plugins/picture-in-picture/back.js +++ b/plugins/picture-in-picture/back.js @@ -23,7 +23,13 @@ const togglePiP = async (win) => { win.setFullScreenable(false); await win.webContents.executeJavaScript( // Go fullscreen - `document.querySelector(".fullscreen-button").click()` + ` + 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); @@ -36,7 +42,10 @@ const togglePiP = async (win) => { } else { await win.webContents.executeJavaScript( // Exit fullscreen - `document.querySelector(".exit-fullscreen-button").click()` + ` + document.querySelector(".exit-fullscreen-button").click(); + document.querySelector("ytmusic-player-bar").classList.remove("pip"); + ` ); win.setVisibleOnAllWorkspaces(false); From d37e557f79b9f9800928e475fb8c9c51f4ad9935 Mon Sep 17 00:00:00 2001 From: TC Date: Sat, 9 Apr 2022 12:15:38 +0200 Subject: [PATCH 10/13] Block some shortcuts (esc, f) in PiP --- plugins/picture-in-picture/back.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/picture-in-picture/back.js b/plugins/picture-in-picture/back.js index 5effa79c..0024b85e 100644 --- a/plugins/picture-in-picture/back.js +++ b/plugins/picture-in-picture/back.js @@ -20,6 +20,8 @@ const togglePiP = async (win) => { originalPosition = win.getPosition(); originalSize = win.getSize(); + win.webContents.on("before-input-event", blockShortcutsInPiP); + win.setFullScreenable(false); await win.webContents.executeJavaScript( // Go fullscreen @@ -40,6 +42,8 @@ const togglePiP = async (win) => { app.dock?.show(); win.setAlwaysOnTop(true, "screen-saver", 1); } else { + win.webContents.removeListener("before-input-event", blockShortcutsInPiP); + await win.webContents.executeJavaScript( // Exit fullscreen ` @@ -66,3 +70,10 @@ module.exports = (win) => { await togglePiP(win); }); }; + +const blockShortcutsInPiP = (event, input) => { + const blockedShortcuts = ["f", "escape"]; + if (blockedShortcuts.includes(input.key.toLowerCase())) { + event.preventDefault(); + } +}; From 14326d24403be01e9fa1b56b7b5014d0e5b7e40b Mon Sep 17 00:00:00 2001 From: TC Date: Sat, 9 Apr 2022 12:16:30 +0200 Subject: [PATCH 11/13] Only save size/position if not in PiP --- index.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 2fdfac58..21110b5b 100644 --- a/index.js +++ b/index.js @@ -174,7 +174,12 @@ function createMainWindow() { win.on("move", () => { if (win.isMaximized()) return; let position = win.getPosition(); - lateSave("window-position", { x: position[0], y: position[1] }); + const isPiPEnabled = + config.plugins.isEnabled("picture-in-picture") && + config.plugins.getOptions("picture-in-picture")["isInPiP"]; + if (!isPiPEnabled) { + lateSave("window-position", { x: position[0], y: position[1] }); + } }); let winWasMaximized; @@ -187,7 +192,10 @@ function createMainWindow() { winWasMaximized = isMaximized; config.set("window-maximized", isMaximized); } - if (!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], height: windowSize[1], From ebe3baf4bcc75b7e26b3732029d61a9613e8949c Mon Sep 17 00:00:00 2001 From: TC Date: Sat, 9 Apr 2022 16:59:24 +0200 Subject: [PATCH 12/13] Make PiP window bigger (follow up: make it configurable) --- plugins/picture-in-picture/back.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/picture-in-picture/back.js b/plugins/picture-in-picture/back.js index 0024b85e..7cda241f 100644 --- a/plugins/picture-in-picture/back.js +++ b/plugins/picture-in-picture/back.js @@ -10,7 +10,7 @@ let originalPosition; let originalSize; const pipPosition = [10, 10]; -const pipSize = [400, 220]; +const pipSize = [450, 275]; const togglePiP = async (win) => { isInPiPMode = !isInPiPMode; From 6be9b765507a3b965e2aff95c207ad9837c57158 Mon Sep 17 00:00:00 2001 From: TC Date: Sat, 9 Apr 2022 16:59:44 +0200 Subject: [PATCH 13/13] Improve style and remove draggable CSS that blocks login --- plugins/picture-in-picture/style.css | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/plugins/picture-in-picture/style.css b/plugins/picture-in-picture/style.css index d944e4cc..adb962d8 100644 --- a/plugins/picture-in-picture/style.css +++ b/plugins/picture-in-picture/style.css @@ -1,13 +1,11 @@ -/* Make entire window draggable */ -body { - -webkit-app-region: drag; -} -button { - -webkit-app-region: no-drag; -} - 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%); +}