// ==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();