HTML5画中画

向兼容浏览器中的HTML5视频添加画中画按钮

  1. // ==UserScript==
  2. // @name Picture In Picture
  3. // @name:zh-CN HTML5画中画
  4. // @namespace http://github.com/eternal-flame-AD/picture-in-picture/
  5. // @version 0.2
  6. // @homepage http://github.com/eternal-flame-AD/picture-in-picture/
  7. // @description Provide picture in picture functionality to HTML5 videos on supported browsers
  8. // @description:zh-CN 向兼容浏览器中的HTML5视频添加画中画按钮
  9. // @compatible chrome 70+
  10. // @author eternal-flame-AD
  11. // @include *
  12. // @grant GM_info
  13. // @license Apache-2.0
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. if (!("pictureInPictureEnabled" in document)) {
  20. console.log("Your browser does not support picture in picture. Exiting...")
  21. return
  22. }
  23. if (!document.pictureInPictureEnabled) {
  24. console.log("Picture in picture is disabled. Exiting...")
  25. return
  26. }
  27.  
  28. window["PIP_URL_BASE"] = "https://cdn.jsdelivr.net/gh/eternal-flame-AD/picture-in-picture/";
  29.  
  30. function formatURL(url) {
  31. return (!/^(https?:)?\/\//.test(url))?(window['PIP_URL_BASE'] + url):url;
  32. }
  33.  
  34. function createStylesheet(url) {
  35. url = formatURL(url);
  36. let elt = document.createElement('link');
  37. elt.rel = 'stylesheet';
  38. elt.href = url;
  39. document.documentElement.appendChild(elt);
  40. };
  41.  
  42. const locateVideoElement = function _self(DOM=document.body) {
  43. let video = DOM.querySelector("video")
  44. if (video) return video
  45. let iframes = DOM.querySelectorAll("iframe")
  46. for (let iframe of iframes) {
  47. video = _self(iframe.contentDocument.body)
  48. if (video) return video
  49. }
  50. return null
  51. }
  52.  
  53. let loaded = false;
  54. const load = function(target) {
  55. if (loaded) return
  56. loaded = true;
  57. createStylesheet('style.css');
  58. class PIP {
  59. constructor(target) {
  60. this.target = target || locateVideoElement(document.body);
  61. this.outside = false
  62. let holder = document.createElement('div')
  63. holder.className = 'pip-indicator-holder pip-off'
  64. let img = document.createElement('img');
  65. img.className = 'pip-indicator-logo'
  66. img.src = formatURL("logo.svg")
  67. holder.appendChild(img)
  68. document.body.appendChild(holder)
  69. this.off = this.off.bind(this)
  70. this.on = this.on.bind(this)
  71. this.updateTarget = this.updateTarget.bind(this)
  72. holder.onclick = () => {
  73. (this.outside?this.off:this.on)().catch(console.error)
  74. }
  75. this.elem = holder;
  76. }
  77. updateTarget() {
  78. if (!this.target.isConnected) this.target = locateVideoElement(document.body);
  79. let _this = this;
  80. this.target.addEventListener('enterpictureinpicture', function _self() {
  81. if (!_this.target.isConnected) {
  82. _this.target.removeEventListener('enterpictureinpicture', _self)
  83. return
  84. }
  85. _this.outside = true
  86. _this.elem.classList.replace("pip-off","pip-on")
  87. });
  88. this.target.addEventListener('leavepictureinpicture', function _self() {
  89. if (!_this.target.isConnected) {
  90. _this.target.removeEventListener('leavepictureinpicture', _self)
  91. return
  92. }
  93. _this.outside = false
  94. _this.elem.classList.replace("pip-on","pip-off")
  95. });
  96. }
  97. async on() {
  98. this.updateTarget()
  99. await this.target.requestPictureInPicture()
  100. }
  101. async off() {
  102. await document.exitPictureInPicture()
  103. }
  104. }
  105. //new PIP(target)
  106. new PIP() // Fixed ad problem
  107. }
  108.  
  109. {
  110. let observers = []
  111. let observe = function _self(DOM=document.body, _window=window, debug=false) {
  112. const gotVideo = function(video) {
  113. if (video.disablePictureInPicture) return;
  114. console.log("Got video element! Loading...")
  115. load(video)
  116. observers.forEach(obs=>{
  117. obs.disconnect()
  118. })
  119. }
  120. {
  121. // Existing video
  122. let video = locateVideoElement(DOM)
  123. if (video) gotVideo(video)
  124. }
  125. {
  126. // Existing iframe
  127. DOM.querySelectorAll("iframe").forEach(iframe=>{
  128. iframe.contentWindow.onload = function() {
  129. iframe.contentWindow["PIP_OBSERVING"] = true;
  130. _self(iframe.contentDocument.body,iframe.contentWindow)
  131. }
  132. })
  133. }
  134. console.log("No video elements. Watching for changes...")
  135.  
  136. let callback = function(mutationList) {
  137. mutationList.forEach((mutation)=>{
  138. mutation.addedNodes.forEach((node)=>{
  139. if (debug) console.log(node)
  140. const search = function (node,tag) {
  141. if (node.tagName && node.tagName.toUpperCase()==tag.toUpperCase()) {
  142. return node
  143. }
  144. return node.querySelector && node.querySelector(tag)
  145. }
  146. {
  147. // Dynamically added video
  148. let video = search(node, "video")
  149. if (video) gotVideo(video)
  150. }
  151.  
  152. {
  153. // Dynamically added iframe
  154. let iframe = search(node, "iframe")
  155. if (iframe) {
  156. iframe.contentWindow.onload = function() {
  157. if (iframe.contentWindow["PIP_OBSERVING"] == true) return;
  158. iframe.contentWindow["PIP_OBSERVING"] = true;
  159. _self(iframe.contentDocument.body,iframe.contentWindow)
  160. }
  161. }
  162. }
  163.  
  164. })
  165. })
  166. }
  167. let observer = new _window.MutationObserver(callback)
  168. observer.observe(DOM,{
  169. childList: true,
  170. attributes: false,
  171. subtree: true
  172. })
  173. observers.push(observer)
  174. window.observers = observers;
  175. }
  176. observe(document.body)
  177. }
  178.  
  179. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址