// ==UserScript==
// @name PIP Enhancer
// @namespace http://tampermonkey.net/
// @version 2.0
// @description Auto PIP Mode Activation (Configurable, Animated, Control Panel)
// @author OB_BUFF
// @license GPL-3.0
// @match *://*/*
// @grant GM_notification
// @grant GM_registerMenuCommand
// ==/UserScript==
(function () {
'use strict';
let pipActive = false; // 记录是否进入 PIP
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";
// 语言包
const messages = {
"zh": {
"enterPiP": "网页失去焦点,视频进入画中画模式",
"exitPiP": "网页回到前台,退出画中画模式",
"enableAnimation": "启用动画",
"disableAnimation": "禁用动画",
"enableNotifications": "启用通知",
"disableNotifications": "禁用通知",
"pipThreshold": "设置画中画触发比例(当前: {value})",
"menuTitle": "PIP 画中画增强设置"
},
"en": {
"enterPiP": "Page lost focus, video entered Picture-in-Picture mode",
"exitPiP": "Page is back in focus, exiting Picture-in-Picture mode",
"enableAnimation": "Enable Animation",
"disableAnimation": "Disable Animation",
"enableNotifications": "Enable Notifications",
"disableNotifications": "Disable Notifications",
"pipThreshold": "Set PIP trigger ratio (Current: {value})",
"menuTitle": "PIP Enhancer Settings"
},
"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",
"enableAnimation": "Habilitar animación",
"disableAnimation": "Deshabilitar animación",
"enableNotifications": "Habilitar notificaciones",
"disableNotifications": "Deshabilitar notificaciones",
"pipThreshold": "Establecer proporción de activación de PiP (Actual: {value})",
"menuTitle": "Configuraciones de PIP Enhancer"
}
};
// 获取用户浏览器语言
const userLang = navigator.language.startsWith("zh") ? "zh" :
navigator.language.startsWith("es") ? "es" : "en";
// 控制面板(Tampermonkey 菜单)
GM_registerMenuCommand(messages[userLang].enableAnimation, () => {
pipAnimationEnabled = true;
});
GM_registerMenuCommand(messages[userLang].disableAnimation, () => {
pipAnimationEnabled = false;
});
GM_registerMenuCommand(messages[userLang].enableNotifications, () => {
notificationEnabled = true;
});
GM_registerMenuCommand(messages[userLang].disableNotifications, () => {
notificationEnabled = false;
});
GM_registerMenuCommand(messages[userLang].pipThreshold.replace("{value}", pipThreshold), () => {
let newThreshold = prompt(messages[userLang].pipThreshold.replace("{value}", pipThreshold), pipThreshold);
if (newThreshold !== null) {
pipThreshold = parseFloat(newThreshold);
}
});
/**
* 判断视频是否满足条件
* 1. 正在播放
* 2. 有声音(音量 > 0 或未静音)
* 3. 占屏幕面积 > pipThreshold(可调节)
*/
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
);
}
/**
* 进入画中画模式
*/
async function enterPiP() {
if (pipActive) return;
let videos = document.querySelectorAll("video");
for (let video of videos) {
if (isEligibleVideo(video)) {
try {
await video.requestPictureInPicture();
pipActive = true;
if (notificationEnabled) {
GM_notification({
text: messages[userLang].enterPiP,
title: messages[userLang].menuTitle,
timeout: 5000,
image: iconUrl
});
}
if (pipAnimationEnabled) {
animatePiP(video);
}
} catch (error) {
console.error("无法进入 PiP 模式:", error);
}
break;
}
}
}
/**
* 退出画中画模式
*/
function exitPiP() {
if (!pipActive) return;
if (document.pictureInPictureElement) {
document.exitPictureInPicture();
if (notificationEnabled) {
GM_notification({
text: messages[userLang].exitPiP,
title: messages[userLang].menuTitle,
timeout: 5000,
image: iconUrl
});
}
}
pipActive = false;
}
/**
* 画中画动画(缩放渐变)
*/
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);
}
/**
* 监听网页可见性变化
*/
document.addEventListener("visibilitychange", function () {
if (document.hidden) {
setTimeout(() => {
if (document.hidden) enterPiP();
}, 300);
} else {
exitPiP();
}
});
/**
* 监听窗口焦点变化
*/
window.addEventListener("blur", enterPiP);
window.addEventListener("focus", exitPiP);
})();