Greasy Fork 支持简体中文。

Ultimate Picture-in-Picture Enhancer

Automatically enable PIP mode with smooth transition and a control panel

目前為 2025-02-13 提交的版本,檢視 最新版本

// ==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);

})();