您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically enable PIP mode with a smooth transition and a configurable, centered control panel.
// ==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); })();