Auto Picture-in-Picture on Tab Change

Auto-enables PiP for playing videos in Chromium browsers on tab switch. Requires an initial click to unlock and exits on return.

当前为 2025-02-17 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Auto Picture-in-Picture on Tab Change
  3. // @namespace https://github.com/EzioTheGoat/EzioUserscripts
  4. // @version 1.3.1
  5. // @description Auto-enables PiP for playing videos in Chromium browsers on tab switch. Requires an initial click to unlock and exits on return.
  6. // @author Ezio Auditore
  7. // @license MIT
  8. // @icon https://img.icons8.com/ios-filled/64/000000/picture-in-picture.png
  9. // @match *://*/*
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. "use strict";
  15.  
  16. /**
  17. * Auto PiP for Chromium browsers on tab change.
  18. *
  19. * The script waits for an initial user click to "unlock" the auto-PiP feature,
  20. * satisfying the user gesture requirement enforced by browsers. When the page loses focus,
  21. * if there is an actively playing video, the script requests Picture-in-Picture mode.
  22. * On regaining focus, it exits Picture-in-Picture mode.
  23. *
  24. * Note: PiP mode will not be activated if the video is paused.
  25. */
  26.  
  27. // Flag to indicate that auto-PiP has been unlocked by a user gesture
  28. let unlocked = false;
  29.  
  30. /**
  31. * Unlocks auto-PiP after the first user click.
  32. */
  33. function unlockPiP() {
  34. unlocked = true;
  35. document.removeEventListener("click", unlockPiP);
  36. console.log("Auto-PiP unlocked by user interaction.");
  37. }
  38. document.addEventListener("click", unlockPiP);
  39.  
  40. /**
  41. * Returns the first available video element that is actively playing.
  42. * @returns {HTMLVideoElement|null} The active video element or null if none found.
  43. */
  44. function getActiveVideo() {
  45. const videos = document.querySelectorAll("video");
  46. for (const video of videos) {
  47. if (!video.paused && !video.ended) {
  48. return video;
  49. }
  50. }
  51. return videos.length ? videos[0] : null;
  52. }
  53.  
  54. /**
  55. * Enters Picture-in-Picture mode if an active (playing) video is found.
  56. */
  57. async function enterPiP() {
  58. if (!unlocked) {
  59. console.log(
  60. "Auto-PiP is locked. Please click anywhere on the page to enable it."
  61. );
  62. return;
  63. }
  64. try {
  65. const video = getActiveVideo();
  66. // Only enter PiP if a video exists and it is playing
  67. if (
  68. video &&
  69. !video.paused &&
  70. document.pictureInPictureElement !== video
  71. ) {
  72. await video.requestPictureInPicture();
  73. console.log("Entered Picture-in-Picture mode.");
  74. } else {
  75. console.log("No active (playing) video available for PiP.");
  76. }
  77. } catch (error) {
  78. console.error("Error entering Picture-in-Picture mode:", error);
  79. }
  80. }
  81.  
  82. /**
  83. * Exits Picture-in-Picture mode if active.
  84. */
  85. async function exitPiP() {
  86. try {
  87. if (document.pictureInPictureElement) {
  88. await document.exitPictureInPicture();
  89. console.log("Exited Picture-in-Picture mode.");
  90. }
  91. } catch (error) {
  92. console.error("Error exiting Picture-in-Picture mode:", error);
  93. }
  94. }
  95.  
  96. /**
  97. * Helper function to delay the execution of an action.
  98. * @param {Function} action - The action to perform after the delay.
  99. * @param {number} delay - The delay in milliseconds.
  100. */
  101. function delayedAction(action, delay = 100) {
  102. setTimeout(action, delay);
  103. }
  104.  
  105. // Event listeners for tab focus and blur events to trigger PiP mode changes.
  106. window.addEventListener("blur", () => {
  107. delayedAction(enterPiP);
  108. });
  109.  
  110. window.addEventListener("focus", () => {
  111. delayedAction(exitPiP);
  112. });
  113.  
  114. // Additional visibility change handling for more robust behavior.
  115. document.addEventListener("visibilitychange", () => {
  116. if (document.visibilityState === "hidden") {
  117. delayedAction(enterPiP);
  118. } else if (document.visibilityState === "visible") {
  119. delayedAction(exitPiP);
  120. }
  121. });
  122. })();