ACT.YouTube.DM.PiP-button

Add a PiP button to the player to easy enter Picture-in-Picture mode.

目前為 2022-04-06 提交的版本,檢視 最新版本

  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 20220406.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. if (document.pictureInPictureElement) {
  53. document.exitPictureInPicture();
  54. } else {
  55. document.querySelector("video[src]")?.requestPictureInPicture();
  56. }
  57. event.preventDefault();
  58. event.stopImmediatePropagation();
  59. }, true);
  60. const pip_button_act = pathx => { path.setAttribute('d', pathx); pip_button.innerHTML = svg.outerHTML; };
  61. pip_button_act(path1);
  62.  
  63. // Insert PiP Button (desktop) // Fixed once for unreliable @run-at document-start
  64. document.querySelector(".ytp-miniplayer-button")?.before(pip_button);
  65.  
  66. // Video element initialization
  67. const PiP_Fix = e => e.stopPropagation();
  68. const onEnterPip = e => pip_button_act(path2);
  69. const onExitPip = e => pip_button_act(path1);
  70. const pip_init = video => {
  71. if (!video || video.nodeName != 'VIDEO' || !video.hasAttribute("src")) return;
  72. video.addEventListener('webkitpresentationmodechanged', PiP_Fix, true);
  73. video.addEventListener("leavepictureinpicture", onExitPip);
  74. video.addEventListener('enterpictureinpicture', onEnterPip);
  75. }
  76. pip_init(document.querySelector('video[src]'));
  77.  
  78. // Dynamic adjustment
  79. new MutationObserver(mutationList => {
  80. mutationList.forEach((mutation) => {
  81. if (mutation.type == 'childList') {
  82. mutation.addedNodes.forEach(node => {
  83. if (node.nodeType != Node.ELEMENT_NODE) return;
  84. node.nodeName == 'VIDEO' && pip_init(node);
  85. node.classList.contains("ytp-miniplayer-button") && node.before(pip_button); // Insert PiP Button (desktop)
  86. });
  87. mutation.removedNodes.forEach(node => {
  88. if (node.nodeType != Node.ELEMENT_NODE) return;
  89. node.classList.contains("ytp-miniplayer-button") && pip_button.remove();
  90. node.id == "player-control-overlay" && pip_button.remove();
  91. });
  92. }
  93. if (mutation.type == 'attributes') {
  94. mutation.target.nodeName == 'VIDEO' && mutation.attributeName == 'src' && pip_init(mutation.target);
  95. if (mutation.target.id == "player-control-overlay" && mutation.attributeName == 'class') { // Insert PiP Button (mobile)
  96. mutation.target.classList.contains("fadein") ? document.querySelector('#player')?.append(pip_button) : pip_button.remove();
  97. }
  98. if (mutation.attributeName == 'class' && mutation.target == document.querySelector('.player-controls-top')?.parentNode) {
  99. mutation.target.classList.contains('player-controls-hide') ? pip_button.remove() : (
  100. document.querySelector('#player-control-overlay')?.classList.contains("fadein") && document.querySelector('#player')?.append(pip_button)
  101. );
  102. }
  103. if (mutation.target.id == "player" && mutation.attributeName == 'hidden') {
  104. mutation.target.hasAttribute('hidden') && (pip_button.remove(), document.exitPictureInPicture());
  105. }
  106. }
  107. });
  108. }).observe(document, { subtree: true, childList: true, attributes: true });
  109.  
  110. })();