您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically enables picture-in-picture mode for YouTube and Bilibili with improved Edge and Brave support
当前为
// ==UserScript== // @name Auto Picture-in-Picture // @namespace http://tampermonkey.net/ // @version 1.1 // @description Automatically enables picture-in-picture mode for YouTube and Bilibili with improved Edge and Brave support // @author // @license MIT // @icon https://raw.githubusercontent.com/hong-tm/blog-image/main/picture-in-picture.svg // @match https://www.youtube.com/* // @match https://www.bilibili.com/* // @grant GM_log // @run-at document-start // ==/UserScript== (function () { "use strict"; // Enhanced logging that works in all browsers const debug = { log: (...args) => { console.log("[PiP Debug]", ...args); try { GM_log(...args); } catch (e) { // Fallback if GM_log isn't available } }, error: (...args) => { console.error("[PiP Error]", ...args); try { GM_log("ERROR:", ...args); } catch (e) { // Fallback if GM_log isn't available } }, }; let isTabActive = !document.hidden; let isPiPRequested = false; let pipInitiatedFromOtherTab = false; let lastInteractionTime = 0; let pipAttempts = 0; const MAX_PIP_ATTEMPTS = 5; const PIP_RETRY_DELAY = 500; // Enhanced browser detection const browserInfo = { get isEdge() { return navigator.userAgent.includes("Edg/"); }, get isBrave() { return ( window.navigator.brave?.isBrave || // Direct Brave API check navigator.userAgent.includes("Brave") || document.documentElement.dataset.browserType === "brave" ); }, get isChrome() { return ( navigator.userAgent.includes("Chrome") && !this.isEdge && !this.isBrave ); }, get isFirefox() { return navigator.userAgent.includes("Firefox"); }, get isChromiumBased() { return this.isChrome || this.isEdge || this.isBrave; }, }; // Enhanced video element detection async function getVideoElement(retryCount = 0, maxRetries = 10) { const selectors = { "youtube.com": [ ".html5-main-video", "video.video-stream", "#movie_player video", ], "bilibili.com": [ ".bilibili-player-video video", "#bilibili-player video", "video", ], }; const domain = Object.keys(selectors).find((d) => window.location.hostname.includes(d) ); if (!domain) return null; let video = null; for (const selector of selectors[domain]) { video = document.querySelector(selector); if (video) break; } if (!video && retryCount < maxRetries) { debug.log( `Video element not found, retrying... (${retryCount + 1}/${maxRetries})` ); await new Promise((resolve) => setTimeout(resolve, 200)); return getVideoElement(retryCount + 1, maxRetries); } debug.log( video ? "Video element found!" : "Failed to find video element after retries." ); return video; } function isVideoPlaying(video) { if (!video) return false; const playing = !video.paused && !video.ended && video.readyState > 2 && video.currentTime > 0; debug.log(`Is video playing: ${playing}`); return playing; } // Enhanced PiP request for Chromium-based browsers async function requestChromiumPiP(video) { if (!video) return false; debug.log( `Attempting PiP on ${ browserInfo.isBrave ? "Brave" : browserInfo.isEdge ? "Edge" : "Chrome" }...` ); try { // Special handling for Brave/Edge if (browserInfo.isBrave || browserInfo.isEdge) { // Force a user interaction context video.focus(); await new Promise((resolve) => setTimeout(resolve, 200)); // Ensure video is playing if (video.paused) { await video.play().catch(() => {}); } } if (document.pictureInPictureEnabled) { await video.requestPictureInPicture(); debug.log("PiP activated successfully!"); pipAttempts = 0; return true; } else { throw new Error("PiP not enabled in browser"); } } catch (error) { debug.error("PiP request failed:", error.message); pipAttempts++; if (pipAttempts < MAX_PIP_ATTEMPTS) { debug.log(`Retrying PiP (attempt ${pipAttempts})...`); await new Promise((resolve) => setTimeout(resolve, PIP_RETRY_DELAY)); return requestChromiumPiP(video); } else { debug.error("Max PiP attempts reached"); return false; } } } async function enablePiP(forceEnable = false) { try { const video = await getVideoElement(); if (!video || (!forceEnable && !isVideoPlaying(video))) { debug.log("Video not ready for PiP"); return; } if (!document.pictureInPictureElement && !isPiPRequested) { let success = false; if (browserInfo.isChromiumBased) { success = await requestChromiumPiP(video); } else { success = await video .requestPictureInPicture() .then(() => true) .catch((e) => { debug.error("Non-Chromium PiP request failed:", e); return false; }); } if (success) { debug.log("PiP enabled successfully"); isPiPRequested = true; pipInitiatedFromOtherTab = !isTabActive; } } } catch (error) { debug.error("Enable PiP error:", error); } } async function disablePiP() { if (document.pictureInPictureElement && !pipInitiatedFromOtherTab) { try { await document.exitPictureInPicture(); debug.log("PiP mode exited"); isPiPRequested = false; pipAttempts = 0; } catch (error) { debug.error("Exit PiP error:", error); } } } /* function handleUserInteraction(event) { lastInteractionTime = Date.now(); debug.log("User interaction detected:", event.type); if (!isTabActive) { const checkAndEnablePiP = async () => { const video = await getVideoElement(); if (video && isVideoPlaying(video)) { await enablePiP(true); } }; if (browserInfo.isBrave || browserInfo.isEdge) { setTimeout(checkAndEnablePiP, 200); } else { checkAndEnablePiP(); } } } */ async function handleVisibilityChange() { const previousState = isTabActive; isTabActive = !document.hidden; debug.log(`Tab visibility changed: ${isTabActive ? "visible" : "hidden"}`); if (previousState !== isTabActive) { if (isTabActive) { if (!pipInitiatedFromOtherTab) { await disablePiP(); } } else { const video = await getVideoElement(); if (video && isVideoPlaying(video)) { const delay = browserInfo.isChromiumBased ? 200 : 0; setTimeout(() => enablePiP(true), delay); } pipInitiatedFromOtherTab = false; } } } function initializeEventListeners() { debug.log("Initializing event listeners..."); document.addEventListener( "visibilitychange", handleVisibilityChange, false ); /* ["click", "keydown", "mousedown", "touchstart"].forEach((eventType) => { document.addEventListener(eventType, handleUserInteraction, { passive: true, }); }); */ if (window.location.hostname.includes("youtube.com")) { window.addEventListener("yt-navigate-finish", () => { setTimeout(async () => { if (!isTabActive) { const video = await getVideoElement(); if (video && isVideoPlaying(video)) { await enablePiP(); } } }, 1000); }); } document.addEventListener("enterpictureinpicture", () => { pipInitiatedFromOtherTab = !isTabActive; isPiPRequested = true; pipAttempts = 0; debug.log("Entered PiP mode"); }); document.addEventListener("leavepictureinpicture", () => { isPiPRequested = false; pipInitiatedFromOtherTab = false; pipAttempts = 0; debug.log("Left PiP mode"); }); } function initializeMediaSession() { if ("mediaSession" in navigator) { try { navigator.mediaSession.setActionHandler( "enterpictureinpicture", async () => { if (!isTabActive) { await enablePiP(true); } } ); debug.log("Media session PiP handler set"); } catch (error) { debug.log("PiP media session action not supported"); } } } // Enhanced initialization with error handling function initialize() { debug.log("Initializing script..."); debug.log("Browser detected:", { isEdge: browserInfo.isEdge, isBrave: browserInfo.isBrave, isChrome: browserInfo.isChrome, isFirefox: browserInfo.isFirefox, }); try { handleVisibilityChange(); initializeMediaSession(); initializeEventListeners(); debug.log("Initialization complete"); } catch (error) { debug.error("Initialization error:", error); } } // Ensure script runs as early as possible if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initialize); } else { initialize(); } })();