ACT.YouTube.DM.画中画按钮

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

当前为 2022-03-26 提交的版本,查看 最新版本

  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 20220326.2
  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. // Create PiP Button
  21. const i1 = 'url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2221%22%20height%3D%2225%22%3E%3Ctitle%3Epip_reduced%401x%3C%2Ftitle%3E%3Crect%20width%3D%2221%22%20height%3D%2225%22%20fill%3D%22none%22%2F%3E%3Cpath%20d%3D%22M2.5%2C17A1.5%2C1.5%2C0%2C0%2C1%2C1%2C15.5v-9A1.5%2C1.5%2C0%2C0%2C1%2C2.5%2C5h13A1.5%2C1.5%2C0%2C0%2C1%2C17%2C6.5V10h1V6.5A2.5%2C2.5%2C0%2C0%2C0%2C15.5%2C4H2.5A2.5%2C2.5%2C0%2C0%2C0%2C0%2C6.5v9A2.5%2C2.5%2C0%2C0%2C0%2C2.5%2C18H7V17Z%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M18.5%2C11h-8A2.5%2C2.5%2C0%2C0%2C0%2C8%2C13.5v5A2.5%2C2.5%2C0%2C0%2C0%2C10.5%2C21h8A2.5%2C2.5%2C0%2C0%2C0%2C21%2C18.5v-5A2.5%2C2.5%2C0%2C0%2C0%2C18.5%2C11Z%22%20fill%3D%22%23fff%22%2F%3E%3C%2Fsvg%3E")';
  22. const i2 = 'url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2221%22%20height%3D%2225%22%3E%3Ctitle%3Epip.fill_reduced%401x%3C%2Ftitle%3E%3Crect%20width%3D%2221%22%20height%3D%2225%22%20fill%3D%22none%22%2F%3E%3Cpath%20d%3D%22M18.5%2C11H18v1h.5A1.5%2C1.5%2C0%2C0%2C1%2C20%2C13.5v5A1.5%2C1.5%2C0%2C0%2C1%2C18.5%2C20h-8A1.5%2C1.5%2C0%2C0%2C1%2C9%2C18.5V18H8v.5A2.5%2C2.5%2C0%2C0%2C0%2C10.5%2C21h8A2.5%2C2.5%2C0%2C0%2C0%2C21%2C18.5v-5A2.5%2C2.5%2C0%2C0%2C0%2C18.5%2C11Z%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M14.5%2C4H2.5A2.5%2C2.5%2C0%2C0%2C0%2C0%2C6.5v8A2.5%2C2.5%2C0%2C0%2C0%2C2.5%2C17h12A2.5%2C2.5%2C0%2C0%2C0%2C17%2C14.5v-8A2.5%2C2.5%2C0%2C0%2C0%2C14.5%2C4Z%22%20fill%3D%22%23fff%22%2F%3E%3C%2Fsvg%3E")';
  23. const pip_button = document.createElement("button");
  24. pip_button.title = "Picture-in-Picture";
  25. pip_button.style.width = "50px";
  26. pip_button.style.height = "50px";
  27. if (location.hostname == "m.youtube.com") {
  28. pip_button.style.setProperty("position", "absolute");
  29. pip_button.style.setProperty("z-index", "100");
  30. } else {
  31. pip_button.className = "ytp-button";
  32. }
  33. pip_button.style.setProperty("background-repeat", "no-repeat");
  34. pip_button.style.setProperty("background-position", "50% 50%");
  35. pip_button.style.setProperty("background-image", i1);
  36. const onEnterPip = e => pip_button.style.setProperty("background-image", i2);
  37. const onExitPip = e => pip_button.style.setProperty("background-image", i1);
  38. if (document.pictureInPictureEnabled) {
  39. pip_button.addEventListener("click", event => {
  40. if (document.pictureInPictureElement) {
  41. document.exitPictureInPicture();
  42. } else {
  43. document.querySelector("video[src]")?.requestPictureInPicture();
  44. }
  45. event.preventDefault();
  46. event.stopImmediatePropagation();
  47. });
  48. } else {
  49. pip_button.style.setProperty("opacity", "0.5");
  50. console.log('Your browser cannot use picture-in-picture right now');
  51. }
  52.  
  53. // Insert PiP Button (desktop) // Fixed once for unreliable @run-at document-start
  54. document.querySelector(".ytp-miniplayer-button")?.before(pip_button);
  55.  
  56. // Video element initialization
  57. const pip_init = video => {
  58. if (!video || video.nodeName != 'VIDEO' || !video.hasAttribute("src")) return;
  59. video.addEventListener('webkitpresentationmodechanged', e => e.stopPropagation(), true); // PiP Fix
  60. video.addEventListener("leavepictureinpicture", onExitPip);
  61. video.addEventListener('enterpictureinpicture', onEnterPip);
  62. }
  63. pip_init(document.querySelector('video[src]')); // Fixed once for unreliable @run-at document-start
  64.  
  65. // Dynamic adjustment
  66. new MutationObserver(mutationList => {
  67. mutationList.forEach((mutation) => {
  68. if (mutation.type == 'childList') {
  69. mutation.addedNodes.forEach(node => {
  70. if (node.nodeType != Node.ELEMENT_NODE) return;
  71. node.nodeName == 'VIDEO' && pip_init(node);
  72. if (node.id == "player-control-overlay") { // Insert PiP Button (mobile)
  73. new MutationObserver(() => {
  74. node.classList.contains("fadein") && node.append(pip_button);
  75. }).observe(node, { attributes: true });
  76. }
  77. node.classList.contains("ytp-miniplayer-button") && node.before(pip_button); // Insert PiP Button (desktop)
  78. })
  79. }
  80. if (mutation.type == 'attributes') { // Enter video from the homepage
  81. mutation.target.nodeName == 'VIDEO' && mutation.attributeName == 'src' && pip_init(mutation.target);
  82. }
  83. });
  84. }).observe(document, { subtree: true, childList: true, attributes: true });
  85.  
  86. })();