ACT.YouTube.DM.画中画按钮

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

当前为 2022-04-09 提交的版本,查看 最新版本

  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 20220409.1
  8. // @license GPL-3.0-or-later
  9. // @namespace ACTCD/Userscripts
  10. // @supportURL https://github.com/ACTCD/Userscripts#contact
  11. // @homepageURL https://github.com/ACTCD/Userscripts
  12. // @match *://*.youtube.com/*
  13. // @grant none
  14. // @run-at document-start
  15. // ==/UserScript==
  16.  
  17. (function () {
  18. 'use strict';
  19.  
  20. if (!document.pictureInPictureEnabled) {
  21. console.log('Your browser cannot use picture-in-picture right now');
  22. return;
  23. }
  24.  
  25. // Create PiP Button
  26. const pip_button = document.createElement("button");
  27. pip_button.title = "Picture-in-Picture";
  28. const svg = document.createElement("svg");
  29. svg.setAttribute('width', '100%');
  30. svg.setAttribute('height', '100%');
  31. svg.setAttribute('viewBox', '-8 -6 36 36');
  32. const path = document.createElement("path");
  33. const path1 = "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";
  34. const path2 = "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";
  35. path.setAttribute('fill', '#fff');
  36. svg.append(path);
  37. if (location.hostname == "m.youtube.com") {
  38. pip_button.style.setProperty("position", "absolute");
  39. pip_button.style.setProperty("z-index", "100");
  40. pip_button.style.setProperty("top", '0px');
  41. pip_button.style.setProperty("left", '4px');
  42. svg.setAttribute('width', '42px');
  43. } else {
  44. pip_button.className = "ytp-button";
  45. path.id = 'ACT_PiP_Path';
  46. const use = document.createElement("use");
  47. use.className = 'ytp-svg-shadow';
  48. use.setAttribute('href', '#' + path.id);
  49. path.before(use);
  50. }
  51. pip_button.addEventListener("click", event => {
  52. const video = document.querySelector('video[src]');
  53. if (!video) return;
  54. if (video.webkitPresentationMode === undefined) {
  55. if (document.pictureInPictureElement) {
  56. document.exitPictureInPicture();
  57. } else {
  58. video.requestPictureInPicture();
  59. }
  60. } else {
  61. if (video.webkitPresentationMode != 'inline') {
  62. video.webkitSetPresentationMode('inline');
  63. } else {
  64. video.webkitSetPresentationMode('picture-in-picture');
  65. }
  66. }
  67. event.preventDefault();
  68. event.stopImmediatePropagation();
  69. }, true);
  70. const pip_button_act = pathx => { path.setAttribute('d', pathx); pip_button.innerHTML = svg.outerHTML; };
  71. pip_button_act(path1);
  72.  
  73. // Insert PiP Button (desktop) // Fixed once for unreliable @run-at document-start
  74. document.querySelector(".ytp-miniplayer-button")?.before(pip_button);
  75.  
  76. // Video element initialization
  77. const enterpictureinpicture = e => pip_button_act(path2);
  78. const leavepictureinpicture = e => pip_button_act(path1);
  79. const webkitpresentationmodechanged = event => {
  80. event.target.webkitPresentationMode == 'picture-in-picture' ? pip_button_act(path2) : pip_button_act(path1);
  81. event.stopImmediatePropagation();
  82. }
  83. const pip_init = video => {
  84. if (!video || video.nodeName != 'VIDEO' || !video.hasAttribute("src")) return;
  85. if (video.webkitPresentationMode === undefined) {
  86. video.addEventListener('enterpictureinpicture', enterpictureinpicture);
  87. video.addEventListener("leavepictureinpicture", leavepictureinpicture);
  88. } else {
  89. video.addEventListener('webkitpresentationmodechanged', webkitpresentationmodechanged, true);
  90. }
  91. }
  92. pip_init(document.querySelector('video[src]'));
  93.  
  94. // Dynamic adjustment
  95. new MutationObserver(mutationList => {
  96. mutationList.forEach((mutation) => {
  97. if (mutation.type == 'childList') {
  98. mutation.addedNodes.forEach(node => {
  99. if (node.nodeType != Node.ELEMENT_NODE) return;
  100. node.nodeName == 'VIDEO' && pip_init(node);
  101. node.classList.contains("ytp-miniplayer-button") && node.before(pip_button); // Insert PiP Button (desktop)
  102. });
  103. mutation.removedNodes.forEach(node => {
  104. if (node.nodeType != Node.ELEMENT_NODE) return;
  105. node.classList.contains("ytp-miniplayer-button") && pip_button.remove();
  106. node.id == "player-control-overlay" && pip_button.remove();
  107. });
  108. }
  109. if (mutation.type == 'attributes') {
  110. mutation.target.nodeName == 'VIDEO' && mutation.attributeName == 'src' && pip_init(mutation.target);
  111. if (mutation.target.id == "player-control-overlay" && mutation.attributeName == 'class') { // Insert PiP Button (mobile)
  112. mutation.target.classList.contains("fadein") ? document.querySelector('#player')?.append(pip_button) : pip_button.remove();
  113. }
  114. if (mutation.attributeName == 'class' && mutation.target == document.querySelector('.player-controls-top')?.parentNode) {
  115. mutation.target.classList.contains('player-controls-hide') ? pip_button.remove() : (
  116. document.querySelector('#player-control-overlay')?.classList.contains("fadein") && document.querySelector('#player')?.append(pip_button)
  117. );
  118. }
  119. if (mutation.target.id == "player" && mutation.attributeName == 'hidden') {
  120. mutation.target.hasAttribute('hidden') && (pip_button.remove(), document.exitPictureInPicture());
  121. }
  122. }
  123. });
  124. }).observe(document, { subtree: true, childList: true, attributes: true });
  125.  
  126. })();