- // ==UserScript==
- // @name Ultimate Picture-in-Picture Enhancer
- // @namespace http://tampermonkey.net/
- // @version 3.2
- // @description Automatically enable PIP mode with a smooth transition and a configurable, centered control panel.
- // @author OB_BUFF
- // @license GPL-3.0
- // @match *://*/*
- // @grant GM_notification
- // @grant GM_addStyle
- // @grant GM_registerMenuCommand
- // @grant GM_setValue
- // @grant GM_getValue
- // ==/UserScript==
-
- (function () {
- 'use strict';
-
- // Load saved settings or use defaults
- let pipAnimationEnabled = GM_getValue("pipAnimationEnabled", true);
- let notificationEnabled = GM_getValue("notificationEnabled", true);
- let pipThreshold = GM_getValue("pipThreshold", 0.3);
- let pipActive = false;
- const iconUrl = "https://images.sftcdn.net/images/t_app-icon-m/p/e858578e-7424-4b99-a13f-c57cd65f8017/4229007087/pip-it-picture-in-picture-logo";
-
- // Multi-language support for UI texts
- const messages = {
- "en": {
- "enterPiP": "Page lost focus, video entered Picture-in-Picture mode",
- "exitPiP": "Page is back in focus, exiting Picture-in-Picture mode",
- "pipSettings": "PIP Enhancer Settings",
- "enableAnimation": "Enable Animation",
- "disableAnimation": "Disable Animation",
- "enableNotifications": "Enable Notifications",
- "disableNotifications": "Disable Notifications",
- "pipThreshold": "PIP Trigger Ratio"
- },
- "zh": {
- "enterPiP": "网页失去焦点,视频进入画中画模式",
- "exitPiP": "网页回到前台,退出画中画模式",
- "pipSettings": "画中画增强设置",
- "enableAnimation": "启用动画",
- "disableAnimation": "禁用动画",
- "enableNotifications": "启用通知",
- "disableNotifications": "禁用通知",
- "pipThreshold": "PIP 触发比例"
- },
- "es": {
- "enterPiP": "La página perdió el foco, el video entró en modo PiP",
- "exitPiP": "La página volvió a enfocarse, saliendo del modo PiP",
- "pipSettings": "Configuración de PIP Enhancer",
- "enableAnimation": "Habilitar animación",
- "disableAnimation": "Deshabilitar animación",
- "enableNotifications": "Habilitar notificaciones",
- "disableNotifications": "Deshabilitar notificaciones",
- "pipThreshold": "Proporción de activación de PiP"
- }
- };
-
- // Detect browser language (default to English)
- const userLang = navigator.language.startsWith("zh") ? "zh" :
- navigator.language.startsWith("es") ? "es" : "en";
-
- // Save current settings
- function saveSettings() {
- GM_setValue("pipAnimationEnabled", pipAnimationEnabled);
- GM_setValue("notificationEnabled", notificationEnabled);
- GM_setValue("pipThreshold", pipThreshold);
- }
-
- // Add a single menu command to open the control panel
- GM_registerMenuCommand(messages[userLang].pipSettings, openControlPanel);
-
- /**
- * Checks if a video meets the PIP criteria:
- * - Playing
- * - Has sound (volume > 0 and not muted)
- * - Covers at least pipThreshold of the screen area
- */
- function isEligibleVideo(video) {
- const rect = video.getBoundingClientRect();
- const screenArea = window.innerWidth * window.innerHeight;
- const videoArea = rect.width * rect.height;
- return (
- !video.paused &&
- video.volume > 0 && !video.muted &&
- (videoArea / screenArea) > pipThreshold
- );
- }
-
- /**
- * Enters Picture-in-Picture mode.
- */
- async function enterPiP() {
- if (pipActive) return;
- const videos = document.querySelectorAll("video");
- for (let video of videos) {
- if (isEligibleVideo(video)) {
- try {
- if (pipAnimationEnabled) animatePiP(video);
- await video.requestPictureInPicture();
- pipActive = true;
- if (notificationEnabled) {
- GM_notification({
- text: messages[userLang].enterPiP,
- title: messages[userLang].pipSettings,
- timeout: 5000,
- image: iconUrl
- });
- }
- } catch (error) {
- console.error("Unable to enter PIP mode:", error);
- }
- break;
- }
- }
- }
-
- /**
- * Exits Picture-in-Picture mode.
- */
- function exitPiP() {
- if (!pipActive) return;
- if (document.pictureInPictureElement) {
- document.exitPictureInPicture();
- if (notificationEnabled) {
- GM_notification({
- text: messages[userLang].exitPiP,
- title: messages[userLang].pipSettings,
- timeout: 5000,
- image: iconUrl
- });
- }
- }
- pipActive = false;
- }
-
- /**
- * Applies a smooth animation effect to the video element before PIP activation.
- */
- function animatePiP(video) {
- video.style.transition = "transform 0.7s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.7s cubic-bezier(0.4, 0, 0.2, 1)";
- video.style.transform = "scale(0.9)";
- video.style.opacity = "0.8";
- setTimeout(() => {
- video.style.transform = "scale(1)";
- video.style.opacity = "1";
- }, 700);
- }
-
- /**
- * Opens a centered HTML control panel that allows users to configure settings.
- */
- function openControlPanel() {
- // Create panel container
- let panel = document.createElement("div");
- panel.id = "pip-control-panel";
- panel.innerHTML = `
- <div class="pip-panel-inner">
- <h2>${messages[userLang].pipSettings}</h2>
- <div>
- <label>
- <input type="checkbox" id="pipAnimationCheckbox">
- ${messages[userLang].enableAnimation}
- </label>
- </div>
- <div>
- <label>
- <input type="checkbox" id="pipNotificationsCheckbox">
- ${messages[userLang].enableNotifications}
- </label>
- </div>
- <div>
- <label>
- ${messages[userLang].pipThreshold}:
- <input type="number" id="pipThresholdInput" value="${pipThreshold}" step="0.1" min="0" max="1">
- </label>
- </div>
- <button id="pipSaveSettings">Save</button>
- <button id="pipClosePanel">Close</button>
- </div>
- `;
- document.body.appendChild(panel);
- }
-
- // Add some CSS for the control panel using GM_addStyle
- GM_addStyle(`
- #pip-control-panel {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: #222;
- color: #fff;
- padding: 20px;
- border-radius: 8px;
- z-index: 10000;
- box-shadow: 0 4px 12px rgba(0,0,0,0.5);
- width: 300px;
- font-family: sans-serif;
- }
- #pip-control-panel .pip-panel-inner {
- text-align: center;
- }
- #pip-control-panel h2 {
- margin-top: 0;
- font-size: 20px;
- }
- #pip-control-panel label {
- display: block;
- margin: 10px 0;
- font-size: 14px;
- }
- #pip-control-panel input[type="number"] {
- width: 60px;
- margin-left: 5px;
- }
- #pip-control-panel button {
- margin: 10px 5px 0;
- padding: 5px 10px;
- background: #555;
- border: none;
- border-radius: 4px;
- color: #fff;
- cursor: pointer;
- }
- #pip-control-panel button:hover {
- background: #666;
- }
- `);
-
- // Event delegation for the control panel buttons (using event listeners on document)
- document.addEventListener("click", function (e) {
- if (e.target && e.target.id === "pipSaveSettings") {
- // Save the settings from the control panel
- pipAnimationEnabled = document.getElementById("pipAnimationCheckbox").checked;
- notificationEnabled = document.getElementById("pipNotificationsCheckbox").checked;
- pipThreshold = parseFloat(document.getElementById("pipThresholdInput").value);
- saveSettings();
- document.getElementById("pip-control-panel").remove();
- }
- if (e.target && e.target.id === "pipClosePanel") {
- document.getElementById("pip-control-panel").remove();
- }
- });
-
- // When the control panel is opened, pre-check the current settings.
- document.addEventListener("click", function (e) {
- if (e.target && e.target.id === "pip-control-panel") {
- // do nothing here
- }
- });
-
- // Pre-populate control panel checkboxes when panel is added.
- const observer = new MutationObserver((mutationsList, observer) => {
- const panel = document.getElementById("pip-control-panel");
- if (panel) {
- document.getElementById("pipAnimationCheckbox").checked = pipAnimationEnabled;
- document.getElementById("pipNotificationsCheckbox").checked = notificationEnabled;
- }
- });
- observer.observe(document.body, { childList: true });
-
- /**
- * Listen for visibility changes to trigger PIP.
- */
- document.addEventListener("visibilitychange", function () {
- if (document.hidden) {
- setTimeout(() => {
- if (document.hidden) enterPiP();
- }, 300);
- } else {
- exitPiP();
- }
- });
-
- /**
- * Listen for window focus changes.
- */
- window.addEventListener("blur", enterPiP);
- window.addEventListener("focus", exitPiP);
-
- })();