ACT.YouTube.DM.画中画按钮

为播放器添加画中画按钮,轻松进入画中画模式。

  1. // ==UserScript==
  2. // @name ACT.YouTube.DM.PiP-button
  3. // @name:zh-CN ACT.YouTube.DM.画中画按钮
  4. // @description Add a PiP button to the player to easy enter Picture-in-Picture mode.
  5. // @description:zh-CN 为播放器添加画中画按钮,轻松进入画中画模式。
  6. // @author ACTCD
  7. // @version 20221105.1
  8. // @license GPL-3.0-or-later
  9. // @namespace ACTCD/Userscripts
  10. // @supportURL https://github.com/ACTCD/Userscripts
  11. // @homepageURL https://github.com/ACTCD/Userscripts
  12. // @match *://*.youtube.com/*
  13. // @grant none
  14. // @inject-into content
  15. // @run-at document-start
  16. // ==/UserScript==
  17.  
  18. (function () {
  19. "use strict";
  20.  
  21. if (!document.pictureInPictureEnabled) {
  22. console.log("Your browser cannot use picture-in-picture right now");
  23. return;
  24. }
  25.  
  26. // Create PiP Button
  27. const pip_button = document.createElement("button");
  28. pip_button.title = "Picture-in-Picture";
  29. const svg = document.createElement("svg");
  30. svg.setAttribute("width", "100%");
  31. svg.setAttribute("height", "100%");
  32. svg.setAttribute("viewBox", "-8 -6 36 36");
  33. const path = document.createElement("path");
  34. const path1 =
  35. "M2.5,17A1.5,1.5,0,0,1,1,15.5v-9A1.5,1.5,0,0,1,2.5,5h13A1.5,1.5,0,0,1,17,6.5V10h1V6.5A2.5,2.5,0,0,0,15.5,4H2.5A2.5,2.5,0,0,0,0,6.5v9A2.5,2.5,0,0,0,2.5,18H7V17Z M18.5,11h-8A2.5,2.5,0,0,0,8,13.5v5A2.5,2.5,0,0,0,10.5,21h8A2.5,2.5,0,0,0,21,18.5v-5A2.5,2.5,0,0,0,18.5,11Z";
  36. const path2 =
  37. "M18.5,11H18v1h.5A1.5,1.5,0,0,1,20,13.5v5A1.5,1.5,0,0,1,18.5,20h-8A1.5,1.5,0,0,1,9,18.5V18H8v.5A2.5,2.5,0,0,0,10.5,21h8A2.5,2.5,0,0,0,21,18.5v-5A2.5,2.5,0,0,0,18.5,11Z M14.5,4H2.5A2.5,2.5,0,0,0,0,6.5v8A2.5,2.5,0,0,0,2.5,17h12A2.5,2.5,0,0,0,17,14.5v-8A2.5,2.5,0,0,0,14.5,4Z";
  38. path.setAttribute("fill", "#fff");
  39. svg.append(path);
  40. if (location.hostname == "m.youtube.com") {
  41. pip_button.style.setProperty("position", "absolute");
  42. pip_button.style.setProperty("z-index", "100");
  43. pip_button.style.setProperty("top", "0px");
  44. pip_button.style.setProperty("left", "4px");
  45. svg.setAttribute("width", "42px");
  46. } else {
  47. pip_button.className = "ytp-button";
  48. path.id = "ACT_PiP_Path";
  49. const use = document.createElement("use");
  50. use.className = "ytp-svg-shadow";
  51. use.setAttribute("href", "#" + path.id);
  52. path.before(use);
  53. }
  54. pip_button.addEventListener(
  55. "click",
  56. (event) => {
  57. const video = document.querySelector("video[src]");
  58. if (!video) return;
  59. if (video.webkitPresentationMode === undefined) {
  60. if (document.pictureInPictureElement) {
  61. document.exitPictureInPicture();
  62. } else {
  63. video.requestPictureInPicture();
  64. }
  65. } else {
  66. if (video.webkitPresentationMode != "inline") {
  67. video.webkitSetPresentationMode("inline");
  68. } else {
  69. video.webkitSetPresentationMode("picture-in-picture");
  70. }
  71. }
  72. event.preventDefault();
  73. event.stopImmediatePropagation();
  74. },
  75. true,
  76. );
  77. const pip_button_act = (pathx) => {
  78. path.setAttribute("d", pathx);
  79. pip_button.innerHTML = svg.outerHTML;
  80. };
  81. pip_button_act(path1);
  82.  
  83. // Insert PiP Button (desktop) // Fixed once for unreliable @run-at document-start
  84. document.querySelector(".ytp-miniplayer-button")?.before(pip_button);
  85.  
  86. // Video element initialization
  87. const enterpictureinpicture = (e) => pip_button_act(path2);
  88. const leavepictureinpicture = (e) => pip_button_act(path1);
  89. const webkitpresentationmodechanged = (event) => {
  90. event.target.webkitPresentationMode == "picture-in-picture"
  91. ? (pip_button_act(path2), event.stopImmediatePropagation())
  92. : pip_button_act(path1);
  93. };
  94. const pip_init = (video) => {
  95. if (!video || video.nodeName != "VIDEO" || !video.hasAttribute("src"))
  96. return;
  97. if (video.webkitPresentationMode === undefined) {
  98. video.addEventListener("enterpictureinpicture", enterpictureinpicture);
  99. video.addEventListener("leavepictureinpicture", leavepictureinpicture);
  100. } else {
  101. video.addEventListener(
  102. "webkitpresentationmodechanged",
  103. webkitpresentationmodechanged,
  104. true,
  105. );
  106. }
  107. };
  108. pip_init(document.querySelector("video[src]"));
  109.  
  110. // Dynamic adjustment
  111. new MutationObserver((mutationList) => {
  112. mutationList.forEach((mutation) => {
  113. if (mutation.type == "childList") {
  114. mutation.addedNodes.forEach((node) => {
  115. if (node.nodeType != Node.ELEMENT_NODE) return;
  116. node.nodeName == "VIDEO" && pip_init(node);
  117. node.classList.contains("ytp-miniplayer-button") &&
  118. node.before(pip_button); // Insert PiP Button (desktop)
  119. });
  120. mutation.removedNodes.forEach((node) => {
  121. if (node.nodeType != Node.ELEMENT_NODE) return;
  122. node.classList.contains("ytp-miniplayer-button") &&
  123. pip_button.remove();
  124. node.id == "player-control-overlay" && pip_button.remove();
  125. });
  126. }
  127. if (mutation.type == "attributes") {
  128. mutation.target.nodeName == "VIDEO" &&
  129. mutation.attributeName == "src" &&
  130. pip_init(mutation.target);
  131. if (
  132. mutation.target.id == "player-control-overlay" &&
  133. mutation.attributeName == "class"
  134. ) {
  135. // Insert PiP Button (mobile)
  136. mutation.target.classList.contains("fadein")
  137. ? document.querySelector("#player")?.append(pip_button)
  138. : pip_button.remove();
  139. }
  140. if (
  141. mutation.attributeName == "class" &&
  142. mutation.target ==
  143. document.querySelector(".player-controls-top")?.parentNode
  144. ) {
  145. mutation.target.classList.contains("player-controls-hide")
  146. ? pip_button.remove()
  147. : document
  148. .querySelector("#player-control-overlay")
  149. ?.classList.contains("fadein") &&
  150. document.querySelector("#player")?.append(pip_button);
  151. }
  152. if (
  153. mutation.target.id == "player" &&
  154. mutation.attributeName == "hidden"
  155. ) {
  156. mutation.target.hasAttribute("hidden") &&
  157. (pip_button.remove(), document.exitPictureInPicture());
  158. }
  159. }
  160. });
  161. }).observe(document, { subtree: true, childList: true, attributes: true });
  162. })();