您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
HTML5播放器,倍速|最高10倍|计时器掌控者|视频跳过广告|视频广告加速
// ==UserScript== // @name 倍速播放 // @namespace https://gitee.com/yellownacl // @version 1.0 // @description HTML5播放器,倍速|最高10倍|计时器掌控者|视频跳过广告|视频广告加速 // @author 黄盐 // @include *:* // @grant none // @run-at document-end // @require https://greasyfork.org/scripts/420615-cangshi-everything-hook/code/Cangshi-Everything-Hook.js?version=893832 // @require https://greasyfork.org/scripts/420618-cangshi-timerhooker/code/Cangshi-TimerHooker.js?version=893838 // ==/UserScript== /* jshint esversion: 6 */ const defaltLocalStorage = {speed: 1, position: {left: 50, top:100}, speedArray: [1, 2, 3, 15]}; // localStorage 默认值 // 因为外部资源脚本不允许 @grant GM_getValue 和 GM_setValue,暂时只能用本地存储替代扩展存储数据。 function GM_getValue(key, defaultValue){ let res = localStorage.getItem('tampermonkeySpeedy') if(res){ res = JSON.parse(res) }else{ res = Object.assign({}, JSON.parse(JSON.stringify(defaltLocalStorage))) } if(res[key]){ return res[key] }else{ return defaultValue } } function GM_setValue(key,value){ let res = localStorage.getItem('tampermonkeySpeedy') if(res){ res = JSON.parse(res) }else{ res = Object.assign({}, JSON.parse(JSON.stringify(defaltLocalStorage))) } res[key] = value localStorage.setItem('tampermonkeySpeedy', JSON.stringify(res)) } function isNumber(obj){ // 这个方法网络上找来的,对于整数,浮点数返回true,对于NaN或可转成NaN的值返回false。 return obj === +obj } function querySelectorAll(parentNode, selector){ let elements = parentNode.querySelectorAll(selector) elements = Array.prototype.slice.call(elements || []) return elements } function changeSpeed(rate, isInit=false){ if(!isNumber(rate)){ rate = parseFloat(rate) if(!isNumber(rate)) { log('不能转为速度') return false } } timer.change(1/rate) if(!isInit){GM_setValue('speed', rate)} return rate } function log(message,msgType="normal"){ let style = { hint: `background:#ff0; border-left: 5px solid #333; padding:1px 3px; font-weight:bold;`, normal: `background:lightgreen;padding:1px 5px 1px 2px; border-right: 3px solid darkblue;`, warning: `background:#FFFBE5;padding:1px 5px 1px 2px; border-right: 3px solid darkblue;`, error: `background:red;padding:1px 5px 1px 2px; border-right: 3px solid darkblue;` }; console.log("%c SpeedyPlay Info: %c"+message, style.hint, style[msgType]); } function getSideSpeed(){ return GM_getValue('speed', 1.0) } function getSitePosition(){ return GM_getValue('position', {left: 50, top: 100}) ; } function saveSpeed(speed){ if(!isNumber(speed)){ log(`速度值设置错误,应该提供数值类型,${speed} 是 ${typeof speed} 类型!`, 'error'); return false; } GM_setValue('speed', speed); return true; } function savePosition(left, top){ try { if(typeof left != 'number' || typeof top != 'number'){ log(`位置值设置错误,应该提供数值类型,现在 left:${typeof left},top: ${typeof top}`, 'error'); return false; } GM_setValue('position', {left: left,top: top}); log(left+'--'+top); return true; } catch (error) {} log("savePosition", left, top) } function saveSpeedArray(speedArray){ GM_setValue('speedArray', speedArray); } function initSpeedy(){ const speedData = { currentSpeed: GM_getValue('speed'), speedArray: GM_getValue('speedArray'), // 常用速度列表,比如2x,3x, 常规观看 10x,15x用来跳过广告。最多6个 editArray: false, // 速度状态是否处于可编辑装填,如果是,点击就没有反应,如果不是,点击就改变速度 lastTimeSpeed: 1, // 上次的速度,方便在2种速度之间切换,区分站点存储, position: GM_getValue('position'), // 控件的位置,区分站点存储 } const speedyHandler = { set(target, prop, value){ switch(prop){ case "currentSpeed" : target["lastTimeSpeed"] = target["currentSpeed"] target[prop] = value currentSpeedElem.value = value speedText.innerHTML = value currentSpeedElem.setAttribute("style", `background-size:${value*10}% 100%`) changeSpeed(value) break; case "editArray": target[prop] = value if(speedState.editArray){ speedArrayElems.forEach(element=>{ element.setAttribute("contenteditable", "true") }) editArrayButton.innerHTML = "保存" } else { let tmp = [] speedArrayElems.forEach(element=>{ element.removeAttribute("contenteditable") tmp.push(parseFloat(element.dataset.value)) }) editArrayButton.innerHTML = "编辑" speedState.speedArray = tmp saveSpeedArray(speedState.speedArray) } break; default: target[prop] = value // log(prop +' → '+ value.toString()) break; } } } // 创建元素 const node = document.createElement("DIV") let nodeHTML = ` <div id="speedCtrl" style="left:${speedData.position.left}px;top:${speedData.position.top}px;"> <div> <button id="speedText">${speedData.currentSpeed}</button> <input id="currentSpeed" type="range" min="0.1" max="10" step="0.1" value="${speedData.currentSpeed}" style="background-size:${speedData.currentSpeed*10}% 100%" > </div> <div> <span class="usualSpeed" data-value="${speedData.speedArray[0]}" data-index="0">${speedData.speedArray[0]}</span> <span class="usualSpeed" data-value="${speedData.speedArray[1]}" data-index="1">${speedData.speedArray[1]}</span> <span class="usualSpeed" data-value="${speedData.speedArray[2]}" data-index="2">${speedData.speedArray[2]}</span> <span class="usualSpeed" data-value="${speedData.speedArray[3]}" data-index="3">${speedData.speedArray[3]}</span> <button id="editArray" class="usualfn">编辑</button> <!-- <button id="expandCtrl" class="usualfn">展开</button> --> </div> </div> <style> #speedCtrl{ opacity: 0.1; width: 50px; height: 50px; overflow: hidden; display: block; position: fixed; top: 100px; left: 50px; display: grid; grid-template-rows: 50px 50px; z-index: 99999999; background: #ffff0010; border-radius: 5px; } #speedCtrl:hover{ opacity: 1; background: #ffff0080; width: auto; height: auto; } #speedCtrl div{ display: flex; align-items: center; justify-content: space-evenly; } #speedText{ cursor: move; display: inline-flex; justify-content: center; align-items: center; width: 50px; height: 50px; border-radius: 25px; border: 2px solid #FDC02F; font-size: 30px; text-align: center; font-weight: bold; background: yellow; } .usualfn, .usualSpeed{ display: inline-flex; justify-content: center; align-items: center; width: 50px; height: 30px; background: yellow; border: 2px solid #FDC02F; border-radius: 5px; font-family: "微软雅黑", consolas; } .usualfn{ cursor: pointer; } /*横条样式*/ #speedCtrl input[type='range'] { -webkit-appearance: none; /*清除系统默认样式*/ border: 1px solid #FDC02F; border-radius: 10px; width: 300px; background: -webkit-linear-gradient(#ff0, #ff0) no-repeat, #999; /*设置左边颜色为#ff0,右边颜色为#999*/ /* background-size: 75% 100%; 设置左右宽度比例 */ height: 20px; /*横条的高度*/ } /*拖动块的样式*/ #speedCtrl input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; /*清除系统默认样式*/ height: 40px; /*拖动块高度*/ width: 40px; /*拖动块宽度*/ background: #fff; /*拖动块背景*/ border-radius: 50%; /*外观设置为圆形*/ border: solid 2px cyan; /*设置边框*/ cursor: pointer; } </style>`; node.innerHTML = nodeHTML; document.body.appendChild(node) function move(e){ let moveTarget = document.getElementById("speedCtrl"); //获取目标元素 //算出鼠标相对元素的位置 let disX = e.clientX - moveTarget.offsetLeft; let disY = e.clientY - moveTarget.offsetTop; let left, top; document.onmousemove = (e)=>{ //鼠标按下并移动的事件 //用鼠标的位置减去鼠标相对元素的位置,得到元素的位置 left = e.clientX - disX; top = e.clientY - disY; speedState.top = top; speedState.left = left; //移动当前元素 moveTarget.style.left = left + 'px'; moveTarget.style.top = top + 'px'; }; document.onmouseup = (e) => { savePosition(left, top) document.onmousemove = null; document.onmouseup = null; }; } const speedState = new Proxy(speedData, speedyHandler) const speedText = document.getElementById("speedText") const currentSpeedElem = document.getElementById("currentSpeed") const speedArrayElems = querySelectorAll(document, ".usualSpeed") const editArrayButton = document.getElementById("editArray") speedText.addEventListener('dblclick', e=>{ speedState.currentSpeed = speedState.lastTimeSpeed }) speedText.addEventListener("mousedown", move) currentSpeedElem.addEventListener('input', e=>{ speedState.currentSpeed = e.target.value }) speedArrayElems.forEach(element => { element.addEventListener('click', e=>{ if(!speedState.editArray){ speedState.currentSpeed = e.target.dataset.value } }) element.addEventListener('blur', e=>{ let rate = parseFloat(e.target.textContent) if(isNumber(rate)){ e.target.dataset.value = rate.toFixed(1) e.target.textContent = rate.toFixed(1) }else{ let input = prompt("输入不正确,请输入数字") if(isNumber(input)){ e.target.dataset.value = rate.toFixed(1) e.target.textContent = rate.toFixed(1) }else{ e.target.textContent = e.target.dataset.value } } }) }); editArrayButton.addEventListener("click", e=>{ speedState.editArray = !speedState.editArray }) changeSpeed(GM_getValue('speed'), true) } function isReady(){ let startStamp = new Date().getTime() window.checkVideoTimer = setInterval(()=>{ let videos = querySelectorAll(document, "video"); let nowStamp = new Date().getTime() if(videos.length){ clearInterval(checkVideoTimer); initSpeedy() }else if((nowStamp - startStamp) > 30000){ clearInterval(checkVideoTimer); }else{ log('waiting...'); } },1000); } isReady();