// ==UserScript== // @name video speed // @namespace http://tampermonkey.net/ // @version 20240114.1 // @description try to take over the world! // @author You // @match *://*/* // @grant none // @downloadURL https://update.greasyfork.icu/scripts/484447/video%20speed.user.js // @updateURL https://update.greasyfork.icu/scripts/484447/video%20speed.meta.js // ==/UserScript== class KeepVideoTime { constructor(props) { this.key = "_video_cache_"; this.obj = this.get() || {}; this.id = null; } set(key, time) { this.obj[key] = time; this.store(); } store() { localStorage.setItem(this.key, JSON.stringify(this.obj)); } get() { let data = localStorage.getItem(this.key); if (!data) { return null; } try { data = JSON.parse(data) } catch (e) { return null; } finally { return data; } } keep(getUrlId, video) { if (!this.id) { this.id = setInterval(() => { let vid = getUrlId(); if (vid) { this.set(vid, video.currentTime); } }, 500); } } stop() { if (this.id) { clearInterval(this.id); this.id = null; } } } let KeepVideoTimeInstance = null; let onceMap = {}; let clickDomOnce = function (selector, key) { if (key in onceMap) { return; } onceMap[key] = true; let id = setInterval(() => { let d = selector(); if (d.length) { d[0].click(); clearInterval(id); delete onceMap[key]; } }, 500); } let DefaultAction = { 'default'(video, cache = new Cache()) { return [ { group: "重置", name: "重置", run: false, action() { cache.clear(); } }, { group: "全屏自动隐藏", name: "全屏自动隐藏", run: false, action() { // cache.obj.fullScreenToHide = !cache.obj.fullScreenToHide; } } ] }, "bilibili.com"(video) { return [ { group: "屏幕操作", name: "宽屏", run: false, action() { clickDomOnce(() => document.getElementsByClassName('bpx-player-ctrl-web-enter'), "bpx-player-ctrl-web-enter"); } }, { group: "屏幕操作", name: "全屏", run: false, action() { clickDomOnce(() => document.getElementsByClassName('bpx-player-ctrl-full'), "bpx-player-ctrl-full"); } }, { group: "屏幕操作", name: "undo", run: true, action() { console.log("undo"); } } ] }, 'youtube.com'(videoInstance) { return [ { group: "屏幕操作", name: "宽屏", run: false, action() { let video = videoInstance.getVideo(); if (video.width < window.innerWidth * .8) { clickDomOnce(() => document.getElementsByClassName('ytp-size-button'), "ytp-size-button"); } } }, { group: "屏幕操作", name: "全屏", run: false, action() { clickDomOnce(() => document.getElementsByClassName('ytp-fullscreen-button'), "ytp-fullscreen-button"); } }, { group: "屏幕操作", name: "undo", run: true, action() { console.log("undo"); } }, { name: "记住最后播放时间", action(run, cache) { let video = videoInstance.getVideo(); let getVid = () => { let vid = (location.search.split('?')[1] || "").split('&').map(_=>_.split('=')).filter(_=>_[0]==='v'); if (vid.length === 1) { vid = vid[0][1]; } return vid; } let vid = getVid(); if (!vid || vid.length < 3) { return; } if (!KeepVideoTimeInstance) { KeepVideoTimeInstance = new KeepVideoTime(); if (vid && vid in KeepVideoTimeInstance.obj) { if (video.currentTime < KeepVideoTimeInstance.obj[vid]) { video.currentTime = KeepVideoTimeInstance.obj[vid]; } } } if (run) { KeepVideoTimeInstance.keep(getVid, video); } else { KeepVideoTimeInstance.stop(); } }, run: true, replay: true, } ] } }; function checkWindow() { return window === parent.window; }; class DraggableDom { static Style = { // width: 100px; // height: 100px; backgroundColor: "lightblue", border: "2px solid #4CAF50", margin: "10px", padding: "10px", cursor: "move", position: "fixed", zIndex: 100000, borderRadius: "5px", } constructor(cache, parentDom = document.body) { this.cache = cache; this.toggleKey = this.cache.obj.toggleKey; this.show = cache.obj.show; this.id = `DD_${new Date().getTime()}`; this.dom = null; this.size = { width: 0, height: 0, }; this.mark = null; if (this.cache.obj.top > window.innerHeight) { this.cache.set("top", 0); } if (this.cache.obj.left > window.innerWidth) { this.cache.set("left", 0); } this.style = { ...DraggableDom.Style, width: `${cache.obj.width}px`, height: `${cache.obj.height}px`, top: `${cache.obj.top}px`, left: `${cache.obj.left}px`, opacity: cache.obj.opacity ? cache.obj.opacity : 1, display: cache.obj.show ? 'unset' : 'none', }; this.createDom(parentDom); this.dom.id = this.id; this.bindEvent(); setTimeout(() => { this.size.width = this.dom.scrollWidth + 20; this.size.height = this.dom.scrollHeight + 20; }, 500); } toggleShow(v) { if (typeof v === 'boolean') { this.show = v; } else { this.show = !this.show; } this.dom.style.display = this.show ? 'unset' : 'none'; } getDom() { if (this.dom) { let dom = this.dom.getElementsByClassName('dom'); if (dom.length) { return dom[0]; } } return null; } createDom(parentDom) { // let btn = document.createElement('button'); btn.style.display = 'none'; btn.setAttribute("accessKey", this.toggleKey); btn.onclick = () => { if (this.dom) { this.toggleShow(); } }; this.mark = document.createElement('div'); this.mark.style.width = "100vw"; this.mark.style.height = "100vh"; this.mark.style.position = "fixed"; this.mark.style.left = "0"; this.mark.style.top = "0"; this.mark.style.display = "none"; this.mark.style.zIndex = this.style.zIndex - 1; this.dom = document.createElement('div'); this.dom .setAttribute('draggable', "true"); this.dom.innerHTML = `
按下 <ALT+${this.toggleKey}> 可以[隐藏/显示]当前选项框
` for (let i in this.style) { this.dom.style[i] = this.style[i]; } parentDom.append(this.dom); parentDom.append(this.mark); parentDom.append(btn); } bindEvent() { // JavaScript 代码,实现拖拽功能 let draggableElement = this.dom; let $this = this; draggableElement.ondragstart = function(event) { event.dataTransfer.setData('text/plain', event.target.id); $this.mark.style.display = 'block'; } draggableElement.ondragend = function(event) { $this.mark.style.display = 'none'; } document.body.ondragover = function(event) { event.preventDefault(); }; document.body.ondrop = function(event) { event.preventDefault(); var data = event.dataTransfer.getData('text/plain'); if (!data) { return; } var draggedElement = document.getElementById(data); // 获取鼠标位置 var mouseX = event.clientX; var mouseY = event.clientY; // 设置元素的新位置 draggedElement.style.left = mouseX - draggedElement.offsetWidth / 2 + 'px'; draggedElement.style.top = mouseY - draggedElement.offsetHeight / 2 + 'px'; $this.cache.set("top", parseFloat(draggedElement.style.top)); $this.cache.set("left", parseFloat(draggedElement.style.left)); }; } }; class Cache { static Reg = { Float: /^[0-9]+.[0-9]+$/, Int: /^[0-9]+$/, } static Default = { toggleKey: "X", top: 0, left: 0, width: 'unset', height: 'unset', speed: 1, Action: {}, opacity: 1, show: true, // fullScreenToHide: false, }; constructor(action) { let $this = this; this.key = "_video_speed_cache_"; this.objType = { toggleKey(r) { r = r.toString(); if (r.length > 1) { return r[0]; } else if (r.length < 1) { return "X"; } else { return r; } }, opacity(r) { r = $this.toNumberFloat(r); return r <= 0 ? 0.1 : (r > 1 ? 1 : r); }, top: $this.toNumberFloat, left: $this.toNumberFloat, // width: 'unset', // height: 'unset', speed(r) { return $this.toNumberFloat(r, 1) }, }; this.obj = this.get() || {}; for (let i in Cache.Default) { if (!(i in this.obj)) { this.obj[i] = Cache.Default[i]; } } } toNumberFloat(r, dv) { r = parseFloat(r.toString()); return Cache.Reg.Float.test(r.toFixed(1)) ? r : (dv||0); } toNumberInt(r, dv) { r = parseInt(r.toString()); return Cache.Reg.Int.test(r) ? r : (dv||0); } set(key, value) { if (key in this.objType) { this.obj[key] = this.objType[key](value); } else { this.obj[key] = value; } this.store(); return this; } store() { localStorage.setItem(this.key, JSON.stringify(this.obj)); } checkData(data) { for (let i in this.objType) { data[i] = this.objType[i](data[i]); } return data; } get() { let data = localStorage.getItem(this.key); if (!data) { return null; } try { data = JSON.parse(data) } catch (e) { return null; } finally { return this.checkData(data); } } clear() { setTimeout(() => { localStorage.removeItem(this.key); }, 1000); } } class PlayDom { constructor(video, videoInstance, actions = [], cache = new Cache()) { this.video = video; this.cache = cache; this.draggableDom = new DraggableDom(this.cache, videoInstance.getParentDomMethod()); this.action = {}; this.videoInstance = videoInstance; this.loopActionRun = false; let ret = this.buildAction(actions); this.createDom(ret.actionListForDom); ret.promise().then(() => { this.loopAction(); }); } addListen() { let $this = this; document.addEventListener('fullscreenchange', handleFullscreenChange); let video = null; function handleFullscreenChange() { if (!video) { video = $this.videoInstance.getVideo(); } if (!document.fullscreenElement) { if ($this.cache.obj.Action['全屏自动隐藏'] === '全屏自动隐藏') { $this.draggableDom.toggleShow(true); } return; } let isVideoFullscreen = false; if (video === document.fullscreenElement) { isVideoFullscreen = true; } else { isVideoFullscreen = !!new Array(...document.fullscreenElement.getElementsByTagName($this.videoInstance.baseDom)).filter(v => v=== video).length; if (isVideoFullscreen) { video = document.fullscreenElement; } } if ($this.cache.obj.Action['全屏自动隐藏'] === '全屏自动隐藏') { $this.draggableDom.toggleShow(false); } } } loopAction() { if (this.loopActionRun) return; this.loopActionRun = true; this.addListen(); let fn = () => { let v = this.videoInstance.getVideo(); let update = false; let windowWidth = window.innerWidth; let windowHeight = window.innerHeight; if (windowWidth < this.cache.obj.left) { this.cache.obj.left = windowWidth - (this.draggableDom.size.width || windowWidth); update = true; } if (windowHeight < this.cache.obj.top) { this.cache.obj.top = windowHeight - (this.draggableDom.size.height || windowHeight); update = true; } v.playbackRate = this.cache.obj.speed; if (update) { this.draggableDom.dom.style.left = `${this.cache.obj.left}px`; this.draggableDom.dom.style.top = `${this.cache.obj.top}px`; } this.videoInstance.LoopAction(); } fn(); setInterval(() => { fn(); },500); } buildAction(actions) { let cacheAction = this.cache.obj.Action; let actionList = []; let actionListForDom = {}; let actionListForDom_ = []; actions.forEach(act => { this.action[act.name] = { action:()=>{}, replay: false, } // act = { name: "", action() {}, group: "", run: false } if (!act.group) { act.group = act.name; } if (!(act.group in actionListForDom)) { actionListForDom[act.group] = []; } if (!(act.group in cacheAction)) { cacheAction[act.group] = ""; } else if (cacheAction[act.group] === "") { cacheAction[act.group] = "none_" + new Date().getTime(); } if (act.name === cacheAction[act.group]) { // 该主当前希望运行改方法 actionList.push(act.action); cacheAction[act.group] = act.name; act.run = true; if (act.replay) { this.action[act.name] = { action: act.action, replay: act.replay, }; } } else { if (act.run) { if (cacheAction[act.group] === "") { cacheAction[act.group] = act.name; actionList.push(act.action); if (act.replay) { this.action[act.name] = { action: act.action, replay: act.replay, }; } } else { act.run = false; this.action[act.name] = { action: act.action, replay: false, }; } } else { this.action[act.name] = { action: act.action, replay: false, }; } } if (!act.replay) { this.action[act.name].replay = true; } actionListForDom[act.group].push({ name: act.name, run: act.run }); }); for (let i in actionListForDom) { actionListForDom_.push({ group: i, actions: actionListForDom[i] }); } this.cache.obj.Action = Object.assign(this.cache.obj.Action, cacheAction); this.cache.store(); return { promise: (function (actionLis, cache) { return new Promise(s => s()).then(() => { actionList.forEach(act => act(true, cache)); return true; }); }).bind(null,actionList,this.cache.obj), actionListForDom: actionListForDom_ }; } createDom(actionListForDom) { let styleDom = document.createElement('style'); styleDom.innerHTML = `.play_dom_box { display: flex; padding: 5px; } .play_dom_btn { background: gainsboro; padding: 2px; margin-bottom: 2px; border-radius: 4px; width: 40px; text-align: center; cursor: pointer; } .play_dom_text { flex: 1; line-height: 80px; text-align: center; }`; let boxDom = document.createElement('div'); boxDom.innerHTML = `
-0.1
-0.2
-0.5
${this.cache.obj.speed}
+0.1
+0.2
+0.5
透明 【
${this.cache.obj.opacity}
-0.1
   
+0.1
${actionListForDom.map(act => { // { // group: "", // actions: [{name: '', run: ''}] // } if (act.actions.length > 1) { let dom = act.actions.map(a => { return ` ${a.name}`; }).join(''); return `
${act.group} | ${dom}
` } else { let acti = act.actions[0]; return `
${acti.name}
` } }).join(' ')}
`; let dom = this.draggableDom.getDom(); dom.append(styleDom); dom.append(boxDom); let $this = this; let speedTextDom = boxDom.getElementsByClassName('play_dom_text_speed')[0]; let opacityTextDom = boxDom.getElementsByClassName('play_dom_text_opacity')[0]; new Array(...boxDom.getElementsByClassName('play_dom_btn_opacity')).forEach(btn => { btn.onclick = function () { let sp = +this.innerText; $this.cache.set('opacity', (+$this.cache.obj.opacity + sp).toFixed(1)); opacityTextDom.innerText = $this.cache.obj.opacity; $this.draggableDom.dom.style.opacity = $this.cache.obj.opacity; }; }); new Array(...boxDom.getElementsByClassName('play_dom_btn_speed')).forEach(btn => { btn.onclick = function () { let sp = +this.innerText; $this.cache.set('speed', (+$this.cache.obj.speed + sp).toFixed(2)); speedTextDom.innerText = $this.cache.obj.speed; // $this.video.playbackRate = $this.cache.obj.speed; }; }); new Array(...boxDom.getElementsByClassName('play_dom_check')).forEach(btn => { btn.onchange = function () { let group = this.getAttribute('name'); let actionName = this.getAttribute('tag'); let currentAction = $this.action[actionName]; if (this.checked) { $this.cache.obj.Action[group] = actionName; if (currentAction.replay) { currentAction.action(true, $this.cache.obj); } else { currentAction.action(true, $this.cache.obj); $this.action[actionName].action = () => {}; } } else { $this.cache.obj.Action[group] = ""; if (currentAction.replay) { currentAction.action(false, $this.cache.obj); } } $this.cache.store(); }; }); } } class Video { static getVideoMethod = { default() { let videos = document.getElementsByTagName('video'); let video = false; if (videos.length) { if (videos.length === 1) { video = videos[0]; } else { let ww = parseInt(window.innerWidth * 0.4); let v = videos.filter(v => v.width > ww); if (v.length) { video = v[0]; } } } return video; }, 'youtube.com'() { let videos = document.getElementsByTagName('video'); return videos.length ? videos[0] : false; }, 'bilibili.com'() { let bwpVideos = document.getElementsByTagName('bwp-video'); let trVideos = document.getElementsByTagName('video'); return bwpVideos.length ? bwpVideos[0] : (trVideos.length ? trVideos[0] : false); }, } static getParentDomMethod = { 'bilibili.com'() { let dom = document.getElementsByClassName('bpx-player-video-area'); if (dom.length) { return dom[0]; } window.loopActionBili = true; return document.body; } } static LoopAction = { 'bilibili.com'() { if (!window.loopActionBili) { window.loopActionBili = setInterval(() => { new Array(...document.getElementsByClassName('fixed-header')).forEach(ele => ele.style.zIndex = 1); }, 500); } } } static RetryTime = { 'default': { time: 60, sleepTime: 1000, }, 'youtube.com': { time: 1e5, sleepTime: 3000, }, } constructor() { this.baseDom = null; this.host =null; this.getVideoMethod = () => false; this.getParentDomMethod = () => document.body; this.LoopAction = () => {}; } getVideo() { return this.getVideoMethod(); } init() { return new Promise(s => { setTimeout(() => { this.host = location.hostname.split('.').reverse().slice(0, 2).reverse().join('.'); let RetryTime = this.host in Video.RetryTime ? Video.RetryTime[this.host] : Video.RetryTime['default']; if (this.host in Video.getVideoMethod) { this.getVideoMethod = Video.getVideoMethod[this.host]; } if (this.host in Video.getParentDomMethod) { this.getParentDomMethod = Video.getParentDomMethod[this.host]; } if (this.host in Video.LoopAction) { this.LoopAction = Video.LoopAction[this.host]; } // 尝试推荐方法2次后使用默认方法不断尝试,超过一分钟后停止所有尝试 let times = 2; let nexter = () => { this.getVideoMethod = Video.getVideoMethod["default"]; times = RetryTime.time; let id = setInterval(() => { let video = this.getVideoMethod(); if (video) { clearInterval(id); this.baseDom = video.tagName; s(); } else { times--; if (!times) { clearInterval(id); console.log("放弃,没有希望了"); } } }, RetryTime.sleepTime); }; let id = setInterval(() => { let video = this.getVideoMethod(); if (video) { clearInterval(id); this.baseDom = video.tagName; s(); } else { times--; if (!times) { clearInterval(id); nexter(); } } }, 500); }, 1000); }) } getParentDom() { return this.getParentDomMethod(); } } (function() { 'use strict'; // window.customElements.defineOld = window.customElements.define; // window.customElements.define = function (name, clz) { // clz.prototype.attachShadowOld = clz.prototype.attachShadow; // clz.prototype.attachShadow = function (obj) { // return this.attachShadowOld({...obj||{},mode: 'open'}) // } // return window.customElements.defineOld(name, clz); // } if (checkWindow()) { let video = new Video(); video.init().then(() => { let v = video.getVideo(); let cache = new Cache(); if (v) { console.log("play dom"); window.playDom = new PlayDom(v, video, [ ...(DefaultAction[video.host](video, cache) || []), ...DefaultAction['default'](video, cache), ],cache); } }); } // Your code here... })();