video play util

Universal video control, Control of the first video element of the page, advance and retract and multiply speed.

  1. // ==UserScript==
  2. // @name video play util
  3. // @namespace https://gist.github.com/zqcccc/32eb827d35b8134f32f9dd3f70d9fb26
  4. // @version 0.0.6
  5. // @description Universal video control, Control of the first video element of the page, advance and retract and multiply speed.
  6. // @author zqcccc
  7. // @match https://www.disneyplus.com/*
  8. // @match https://www.bilibili.com/video/*
  9. // @match https://jable.tv/videos/*
  10. // @match https://dsxys.live/player/*
  11. // @match https://web.telegram.org/k/*
  12. // @match https://www.youtube.com/watch*
  13. // @match https://kaiwu.lagou.com/*
  14. // @match https://ddys.art/*
  15. // @icon https://www.google.com/s2/favicons?sz=64&domain=github.com
  16. // @grant none
  17. // @license MIT
  18. // ==/UserScript==
  19.  
  20. ; (function () {
  21. 'use strict'
  22.  
  23. Element.prototype._attachShadow = Element.prototype.attachShadow
  24. Element.prototype.attachShadow = function (option) {
  25. return this._attachShadow(Object.assign(option, { mode: 'open' }))
  26. }
  27.  
  28. function traverseElements(element, cb) {
  29. if (cb(element)) return true
  30.  
  31. // 遍历当前元素的子节点
  32. var children = element.children;
  33. for (var i = 0; i < children.length; i++) {
  34. const res = traverseElements(children[i], cb);
  35. if (res) break
  36. }
  37. }
  38.  
  39. let videoElement
  40. function getFirstVideo() {
  41. if (videoElement) return videoElement
  42. document.documentElement.background = '#fff'
  43. const currentElement = document.querySelectorAll('video')[0] || window.$0 // chrome 自己指定
  44. if (currentElement) {
  45. videoElement = currentElement
  46. } else {
  47. traverseElements(document.documentElement, (element) => {
  48. if (element.shadowRoot) {
  49. const shadowRoot = element.shadowRoot
  50. const shadowVideo = shadowRoot.querySelector('video')
  51. if (shadowVideo) {
  52. videoElement = shadowVideo
  53. return true
  54. }
  55. }
  56. })
  57. }
  58. return videoElement
  59. }
  60.  
  61. function attachKeydownEvent() {
  62. function addEvent() {
  63. document.documentElement.addEventListener('keydown', keydownHandle)
  64. }
  65. function removeEvent() {
  66. document.documentElement.removeEventListener('keydown', keydownHandle)
  67. }
  68.  
  69. const keydownHandle = (e) => {
  70. const videoElement = getFirstVideo()
  71. if (!videoElement) removeEvent()
  72. switch (e.code) {
  73. case 'Period':
  74. videoElement.currentTime += 1
  75. break
  76. case 'Comma':
  77. videoElement.currentTime -= 2
  78. break
  79. case 'Equal':
  80. videoElement.playbackRate += 0.1
  81. break
  82. case 'Minus':
  83. videoElement.playbackRate -= 0.1
  84. break
  85. default:
  86. console.log(e.code)
  87. break
  88. }
  89. }
  90.  
  91. let hasAddEvent = false
  92. function check() {
  93. const videoElement = getFirstVideo()
  94. if (videoElement) {
  95. hasAddEvent = true
  96. emptyHandles()
  97. addEvent()
  98. }
  99. }
  100.  
  101. let intervalTimes = 0
  102. const timer = setInterval(() => {
  103. if (hasAddEvent || intervalTimes >= 120) {
  104. clearInterval(timer)
  105. } else {
  106. check()
  107. }
  108. intervalTimes++
  109. }, 1000)
  110. return removeEvent
  111. }
  112.  
  113. function observeVideo() {
  114. const v = getFirstVideo()
  115. if (v) {
  116. const ob = new MutationObserver((mutations, observer) => {
  117. mutations.forEach((mut) => {
  118. console.log(
  119. '%c mut: ',
  120. 'font-size:12px;background-color: #553E2E;color:#fff;',
  121. mut
  122. )
  123. if (mut.type === 'attributes' && mut.attributeName === 'src') {
  124. console.log('src changed')
  125. setTimeout(() => {
  126. emptyHandles()
  127. addRemove(attachKeydownEvent())
  128. }, 3000)
  129. }
  130. })
  131. })
  132. ob.observe(v, { attributes: true, attributeOldValue: true })
  133. return () => {
  134. ob.disconnect()
  135. }
  136. } else {
  137. console.log('no video element')
  138. }
  139. }
  140.  
  141. var waitRemoveHandles = []
  142. function emptyHandles() {
  143. for (let i = waitRemoveHandles.length - 1; i >= 0; i--) {
  144. waitRemoveHandles.pop()?.()
  145. }
  146. }
  147.  
  148. function insertPanel() {
  149. document.querySelector('#panel-control')?.remove()
  150.  
  151. const panel = document.createElement('div')
  152.  
  153. panel.style.cssText = `
  154. display: flex;
  155. flex-direction: column;
  156. align-items: center;
  157. justify-content: center;
  158. position: fixed;
  159. z-index: 100;
  160. width: 150px;
  161. height: 150px;
  162. background: rgba(0,0,0,0.4);
  163. color: #fff;
  164. `
  165. panel.style.left = '0'
  166. panel.style.top = '50vh'
  167. panel.draggable = 'true'
  168. panel.setAttribute('id', 'panel-control')
  169.  
  170. panel.innerHTML = `<style>
  171. #panel-control {
  172. opacity: 0.2
  173. }
  174. #panel-control:hover {
  175. opacity: 1
  176. }
  177. #panel-control button {
  178. background: rgba(0,0,0,0.5);
  179. border: none;
  180. color: #fff;
  181. }
  182. #refresh-panel {
  183. user-select: none;
  184. cursor: pointer;
  185. position: absolute;
  186. right: 10px;
  187. top: 10px;
  188. }
  189. </style>
  190. <div class="info"></div>
  191. <button class="get-info">info</button>
  192. <button class="add-speed">speed+0.1</button>
  193. <button class="minus-speed">speed-0.1</button>
  194. <button class="forward">+1s</button>
  195. <button class="backward">-2s</button>
  196. <button id="refresh-panel">↻</button>
  197. `
  198.  
  199. panel.querySelector('.get-info')?.addEventListener('click', () => {
  200. const video = getFirstVideo()
  201. if (video) {
  202. const infoDiv = panel.querySelector('.info')
  203. infoDiv.innerHTML = `rate:${Math.floor(video.playbackRate * 100) / 100}`
  204. }
  205. })
  206. panel.querySelector('.add-speed')?.addEventListener('click', () => {
  207. const video = getFirstVideo()
  208. video && (video.playbackRate += 0.1)
  209. })
  210. panel.querySelector('.minus-speed')?.addEventListener('click', () => {
  211. const video = getFirstVideo()
  212. video && (video.playbackRate -= 0.1)
  213. })
  214. panel.querySelector('.forward')?.addEventListener('click', () => {
  215. const video = getFirstVideo()
  216. video && (video.currentTime += 1)
  217. })
  218. panel.querySelector('.backward')?.addEventListener('click', () => {
  219. const video = getFirstVideo()
  220. video && (video.currentTime -= 2)
  221. })
  222. panel.querySelector('#refresh-panel')?.addEventListener('click', () => {
  223. videoElement = null
  224. init()
  225. })
  226.  
  227. let canDrag = false,
  228. lastX,
  229. lastY,
  230. panelLeft,
  231. panelTop
  232.  
  233. panel.addEventListener('dragstart', (e) => {
  234. canDrag = true
  235. lastX = e.pageX
  236. lastY = e.pageY
  237. const rect = panel.getBoundingClientRect()
  238. panelLeft = rect.left
  239. panelTop = rect.top
  240. })
  241. panel.addEventListener('drag', (e) => {
  242. if (canDrag) {
  243. const disX = e.pageX - lastX;
  244. const disY = e.pageY - lastY;
  245. const newLeft = panelLeft + disX;
  246. const newTop = panelTop + disY;
  247.  
  248. // 边界检查
  249. const maxX = window.innerWidth - panel.offsetWidth;
  250. const maxY = window.innerHeight - panel.offsetHeight;
  251. panel.style.left = Math.max(0, Math.min(maxX, newLeft)) + 'px';
  252. panel.style.top = Math.max(0, Math.min(maxY, newTop)) + 'px';
  253. }
  254. })
  255. panel.addEventListener('dragend', () => (canDrag = false))
  256. panel.addEventListener('dragover', function (e) {
  257. e.preventDefault()
  258. })
  259.  
  260. document.documentElement.appendChild(panel)
  261. }
  262.  
  263. function addRemove(fn) {
  264. waitRemoveHandles.push(fn)
  265. }
  266.  
  267. function init() {
  268. emptyHandles()
  269. const cancel = observeVideo()
  270. addRemove(cancel)
  271. insertPanel()
  272. addRemove(attachKeydownEvent())
  273. }
  274.  
  275. init()
  276. })()