video play util

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

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         video play util
// @namespace    https://gist.github.com/zqcccc/32eb827d35b8134f32f9dd3f70d9fb26
// @version      0.0.6
// @description  Universal video control, Control of the first video element of the page, advance and retract and multiply speed.
// @author       zqcccc
// @match        https://www.disneyplus.com/*
// @match        https://www.bilibili.com/video/*
// @match        https://jable.tv/videos/*
// @match        https://dsxys.live/player/*
// @match        https://web.telegram.org/k/*
// @match        https://www.youtube.com/watch*
// @match        https://kaiwu.lagou.com/*
// @match        https://ddys.art/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=github.com
// @grant        none
// @license      MIT
// ==/UserScript==

; (function () {
  'use strict'

  Element.prototype._attachShadow = Element.prototype.attachShadow
  Element.prototype.attachShadow = function (option) {
    return this._attachShadow(Object.assign(option, { mode: 'open' }))
  }

  function traverseElements(element, cb) {
    if (cb(element)) return true

    // 遍历当前元素的子节点
    var children = element.children;
    for (var i = 0; i < children.length; i++) {
      const res = traverseElements(children[i], cb);
      if (res) break
    }
  }

  let videoElement
  function getFirstVideo() {
    if (videoElement) return videoElement
    document.documentElement.background = '#fff'
    const currentElement = document.querySelectorAll('video')[0] || window.$0 // chrome 自己指定
    if (currentElement) {
      videoElement = currentElement
    } else {
      traverseElements(document.documentElement, (element) => {
        if (element.shadowRoot) {
          const shadowRoot = element.shadowRoot
          const shadowVideo = shadowRoot.querySelector('video')
          if (shadowVideo) {
            videoElement = shadowVideo
            return true
          }
        }
      })
    }
    return videoElement
  }

  function attachKeydownEvent() {
    function addEvent() {
      document.documentElement.addEventListener('keydown', keydownHandle)
    }
    function removeEvent() {
      document.documentElement.removeEventListener('keydown', keydownHandle)
    }

    const keydownHandle = (e) => {
      const videoElement = getFirstVideo()
      if (!videoElement) removeEvent()
      switch (e.code) {
        case 'Period':
          videoElement.currentTime += 1
          break
        case 'Comma':
          videoElement.currentTime -= 2
          break
        case 'Equal':
          videoElement.playbackRate += 0.1
          break
        case 'Minus':
          videoElement.playbackRate -= 0.1
          break
        default:
          console.log(e.code)
          break
      }
    }

    let hasAddEvent = false
    function check() {
      const videoElement = getFirstVideo()
      if (videoElement) {
        hasAddEvent = true
        emptyHandles()
        addEvent()
      }
    }

    let intervalTimes = 0
    const timer = setInterval(() => {
      if (hasAddEvent || intervalTimes >= 120) {
        clearInterval(timer)
      } else {
        check()
      }
      intervalTimes++
    }, 1000)
    return removeEvent
  }

  function observeVideo() {
    const v = getFirstVideo()
    if (v) {
      const ob = new MutationObserver((mutations, observer) => {
        mutations.forEach((mut) => {
          console.log(
            '%c mut: ',
            'font-size:12px;background-color: #553E2E;color:#fff;',
            mut
          )
          if (mut.type === 'attributes' && mut.attributeName === 'src') {
            console.log('src changed')
            setTimeout(() => {
              emptyHandles()
              addRemove(attachKeydownEvent())
            }, 3000)
          }
        })
      })
      ob.observe(v, { attributes: true, attributeOldValue: true })
      return () => {
        ob.disconnect()
      }
    } else {
      console.log('no video element')
    }
  }

  var waitRemoveHandles = []
  function emptyHandles() {
    for (let i = waitRemoveHandles.length - 1; i >= 0; i--) {
      waitRemoveHandles.pop()?.()
    }
  }

  function insertPanel() {
    document.querySelector('#panel-control')?.remove()

    const panel = document.createElement('div')

    panel.style.cssText = `
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: fixed;
z-index: 100;
width: 200px;
height: 200px;
background: rgba(0,0,0,0.4);
color: #fff;
`
    panel.style.left = '0'
    panel.style.top = '50vh'
    panel.draggable = 'true'
    panel.setAttribute('id', 'panel-control')

    panel.innerHTML = `<style>
    #panel-control {
      opacity: 0.2
    }
    #panel-control:hover {
      opacity: 1
    }
#panel-control button {
  background: rgba(0,0,0,0.5);
  border: none;
  color: #fff;
}
#refresh-panel {
  user-select: none;
  cursor: pointer;
  position: absolute;
  right: 10px;
  top: 10px;
}
</style>
<div class="info"></div>
<button class="get-info">info</button>
<button class="add-speed">speed+0.1</button>
<button class="minus-speed">speed-0.1</button>
<button class="forward">+1s</button>
<button class="backward">-2s</button>
<button id="refresh-panel">↻</button>
`

    panel.querySelector('.get-info')?.addEventListener('click', () => {
      const video = getFirstVideo()
      if (video) {
        const infoDiv = panel.querySelector('.info')
        infoDiv.innerHTML = `rate:${Math.floor(video.playbackRate * 100) / 100}`
      }
    })
    panel.querySelector('.add-speed')?.addEventListener('click', () => {
      const video = getFirstVideo()
      video && (video.playbackRate += 0.1)
    })
    panel.querySelector('.minus-speed')?.addEventListener('click', () => {
      const video = getFirstVideo()
      video && (video.playbackRate -= 0.1)
    })
    panel.querySelector('.forward')?.addEventListener('click', () => {
      const video = getFirstVideo()
      video && (video.currentTime += 1)
    })
    panel.querySelector('.backward')?.addEventListener('click', () => {
      const video = getFirstVideo()
      video && (video.currentTime -= 2)
    })
    panel.querySelector('#refresh-panel')?.addEventListener('click', () => {
      videoElement = null
      init()
    })

    let canDrag = false,
      lastX,
      lastY,
      panelLeft,
      panelTop

    panel.addEventListener('dragstart', (e) => {
      canDrag = true
      lastX = e.pageX
      lastY = e.pageY
      const rect = panel.getBoundingClientRect()
      panelLeft = rect.left
      panelTop = rect.top
    })
    panel.addEventListener('drag', (e) => {
      if (canDrag) {
        const disX = e.pageX - lastX
        panel.style.left = panelLeft + disX + 'px'
        const disY = e.pageY - lastY
        panel.style.top = panelTop + disY + 'px'
      }
    })
    panel.addEventListener('dragend', () => (canDrag = false))
    panel.addEventListener('dragover', function (e) {
      e.preventDefault()
    })

    document.documentElement.appendChild(panel)
  }

  function addRemove(fn) {
    waitRemoveHandles.push(fn)
  }

  function init() {
    emptyHandles()
    const cancel = observeVideo()
    addRemove(cancel)
    insertPanel()
    addRemove(attachKeydownEvent())
  }

  init()
})()