// ==UserScript==
// @name Ultimate Picture-in-Picture Enhancer
// @namespace http://tampermonkey.net/
// @version 3.1
// @description Automatically enable PIP mode with smooth transition and a control panel
// @author OB_BUFF
// @license GPL-3.0
// @match *://*/*
// @grant GM_notification
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// ==/UserScript==
(function () {
'use strict';
let pipActive = false;
let pipAnimationEnabled = true;
let notificationEnabled = true;
let pipThreshold = 0.3;
let 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
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"
}
};
// Get browser language
const userLang = navigator.language.startsWith("zh") ? "zh" :
navigator.language.startsWith("es") ? "es" : "en";
// Add menu command to open control panel
GM_registerMenuCommand(messages[userLang].pipSettings, () => {
openControlPanel();
});
/**
* Checks if a video meets PIP criteria:
* 1. Playing
* 2. Has sound (volume > 0 and not muted)
* 3. Covers at least pipThreshold of screen
*/
function isEligibleVideo(video) {
let rect = video.getBoundingClientRect();
let screenWidth = window.innerWidth;
let screenHeight = window.innerHeight;
let videoArea = rect.width * rect.height;
let screenArea = screenWidth * screenHeight;
return (
!video.paused &&
video.volume > 0 && !video.muted &&
(videoArea / screenArea) > pipThreshold
);
}
/**
* Enters Picture-in-Picture mode
*/
async function enterPiP() {
if (pipActive) return;
let 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;
}
/**
* Smooth PIP animation (scale and fade)
*/
function animatePiP(video) {
video.style.transition = "transform 0.5s ease-in-out, opacity 0.5s";
video.style.transform = "scale(0.8)";
video.style.opacity = "0.8";
setTimeout(() => {
video.style.transform = "scale(1)";
video.style.opacity = "1";
}, 500);
}
/**
* Open Control Panel (HTML UI)
*/
function openControlPanel() {
let panel = document.createElement("div");
panel.innerHTML = `
<div style="position:fixed;bottom:10px;right:10px;padding:10px;background:#222;color:#fff;border-radius:5px;z-index:9999;">
<h3>${messages[userLang].pipSettings}</h3>
<label><input type="checkbox" id="pipAnimation"> ${messages[userLang].enableAnimation}</label><br>
<label><input type="checkbox" id="pipNotifications"> ${messages[userLang].enableNotifications}</label><br>
<label>${messages[userLang].pipThreshold}: <input type="number" id="pipThreshold" value="${pipThreshold}" step="0.1" min="0" max="1"></label><br>
<button id="closePanel">Close</button>
</div>
`;
document.body.appendChild(panel);
document.getElementById("pipAnimation").checked = pipAnimationEnabled;
document.getElementById("pipNotifications").checked = notificationEnabled;
document.getElementById("pipAnimation").addEventListener("change", (e) => {
pipAnimationEnabled = e.target.checked;
});
document.getElementById("pipNotifications").addEventListener("change", (e) => {
notificationEnabled = e.target.checked;
});
document.getElementById("pipThreshold").addEventListener("input", (e) => {
pipThreshold = parseFloat(e.target.value);
});
document.getElementById("closePanel").addEventListener("click", () => {
panel.remove();
});
}
/**
* Listen for visibility change
*/
document.addEventListener("visibilitychange", function () {
if (document.hidden) {
setTimeout(() => {
if (document.hidden) enterPiP();
}, 300);
} else {
exitPiP();
}
});
/**
* Listen for window focus change
*/
window.addEventListener("blur", enterPiP);
window.addEventListener("focus", exitPiP);
})();