// ==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: 150px;
height: 150px;
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;
const disY = e.pageY - lastY;
const newLeft = panelLeft + disX;
const newTop = panelTop + disY;
// 边界检查
const maxX = window.innerWidth - panel.offsetWidth;
const maxY = window.innerHeight - panel.offsetHeight;
panel.style.left = Math.max(0, Math.min(maxX, newLeft)) + 'px';
panel.style.top = Math.max(0, Math.min(maxY, newTop)) + '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()
})()