倍速播放

HTML5播放器,倍速|最高10倍|计时器掌控者|视频跳过广告|视频广告加速

  1. // ==UserScript==
  2. // @name 倍速播放
  3. // @namespace https://gitee.com/yellownacl
  4. // @version 1.0
  5. // @description HTML5播放器,倍速|最高10倍|计时器掌控者|视频跳过广告|视频广告加速
  6. // @author 黄盐
  7. // @include *:*
  8. // @grant none
  9. // @run-at document-end
  10. // @require https://greasyfork.org/scripts/420615-cangshi-everything-hook/code/Cangshi-Everything-Hook.js?version=893832
  11. // @require https://greasyfork.org/scripts/420618-cangshi-timerhooker/code/Cangshi-TimerHooker.js?version=893838
  12. // ==/UserScript==
  13. /* jshint esversion: 6 */
  14. const defaltLocalStorage = {speed: 1, position: {left: 50, top:100}, speedArray: [1, 2, 3, 15]}; // localStorage 默认值
  15. // 因为外部资源脚本不允许 @grant GM_getValue 和 GM_setValue,暂时只能用本地存储替代扩展存储数据。
  16. function GM_getValue(key, defaultValue){
  17. let res = localStorage.getItem('tampermonkeySpeedy')
  18. if(res){
  19. res = JSON.parse(res)
  20. }else{
  21. res = Object.assign({}, JSON.parse(JSON.stringify(defaltLocalStorage)))
  22. }
  23. if(res[key]){
  24. return res[key]
  25. }else{
  26. return defaultValue
  27. }
  28. }
  29. function GM_setValue(key,value){
  30. let res = localStorage.getItem('tampermonkeySpeedy')
  31. if(res){
  32. res = JSON.parse(res)
  33. }else{
  34. res = Object.assign({}, JSON.parse(JSON.stringify(defaltLocalStorage)))
  35. }
  36. res[key] = value
  37. localStorage.setItem('tampermonkeySpeedy', JSON.stringify(res))
  38. }
  39.  
  40. function isNumber(obj){
  41. // 这个方法网络上找来的,对于整数,浮点数返回true,对于NaN或可转成NaN的值返回false。
  42. return obj === +obj
  43. }
  44.  
  45. function querySelectorAll(parentNode, selector){
  46. let elements = parentNode.querySelectorAll(selector)
  47. elements = Array.prototype.slice.call(elements || [])
  48. return elements
  49. }
  50.  
  51. function changeSpeed(rate, isInit=false){
  52. if(!isNumber(rate)){
  53. rate = parseFloat(rate)
  54. if(!isNumber(rate)) {
  55. log('不能转为速度')
  56. return false
  57. }
  58. }
  59. timer.change(1/rate)
  60. if(!isInit){GM_setValue('speed', rate)}
  61. return rate
  62. }
  63.  
  64. function log(message,msgType="normal"){
  65. let style = {
  66. hint: `background:#ff0; border-left: 5px solid #333; padding:1px 3px; font-weight:bold;`,
  67. normal: `background:lightgreen;padding:1px 5px 1px 2px; border-right: 3px solid darkblue;`,
  68. warning: `background:#FFFBE5;padding:1px 5px 1px 2px; border-right: 3px solid darkblue;`,
  69. error: `background:red;padding:1px 5px 1px 2px; border-right: 3px solid darkblue;`
  70. };
  71. console.log("%c SpeedyPlay Info: %c"+message, style.hint, style[msgType]);
  72. }
  73.  
  74. function getSideSpeed(){
  75. return GM_getValue('speed', 1.0)
  76. }
  77. function getSitePosition(){
  78. return GM_getValue('position', {left: 50, top: 100}) ;
  79. }
  80. function saveSpeed(speed){
  81. if(!isNumber(speed)){
  82. log(`速度值设置错误,应该提供数值类型,${speed} ${typeof speed} 类型!`, 'error');
  83. return false;
  84. }
  85. GM_setValue('speed', speed);
  86. return true;
  87. }
  88. function savePosition(left, top){
  89. try {
  90. if(typeof left != 'number' || typeof top != 'number'){
  91. log(`位置值设置错误,应该提供数值类型,现在 left:${typeof left},top: ${typeof top}`, 'error');
  92. return false;
  93. }
  94. GM_setValue('position', {left: left,top: top});
  95. log(left+'--'+top);
  96. return true;
  97. } catch (error) {}
  98. log("savePosition", left, top)
  99. }
  100. function saveSpeedArray(speedArray){
  101. GM_setValue('speedArray', speedArray);
  102. }
  103. function initSpeedy(){
  104. const speedData = {
  105. currentSpeed: GM_getValue('speed'),
  106. speedArray: GM_getValue('speedArray'), // 常用速度列表,比如2x,3x, 常规观看 10x,15x用来跳过广告。最多6个
  107. editArray: false, // 速度状态是否处于可编辑装填,如果是,点击就没有反应,如果不是,点击就改变速度
  108. lastTimeSpeed: 1, // 上次的速度,方便在2种速度之间切换,区分站点存储,
  109. position: GM_getValue('position'), // 控件的位置,区分站点存储
  110. }
  111. const speedyHandler = {
  112. set(target, prop, value){
  113. switch(prop){
  114. case "currentSpeed" :
  115. target["lastTimeSpeed"] = target["currentSpeed"]
  116. target[prop] = value
  117. currentSpeedElem.value = value
  118. speedText.innerHTML = value
  119. currentSpeedElem.setAttribute("style", `background-size:${value*10}% 100%`)
  120. changeSpeed(value)
  121. break;
  122. case "editArray":
  123. target[prop] = value
  124. if(speedState.editArray){
  125. speedArrayElems.forEach(element=>{
  126. element.setAttribute("contenteditable", "true")
  127. })
  128. editArrayButton.innerHTML = "保存"
  129. } else {
  130. let tmp = []
  131. speedArrayElems.forEach(element=>{
  132. element.removeAttribute("contenteditable")
  133. tmp.push(parseFloat(element.dataset.value))
  134. })
  135. editArrayButton.innerHTML = "编辑"
  136. speedState.speedArray = tmp
  137. saveSpeedArray(speedState.speedArray)
  138. }
  139. break;
  140. default:
  141. target[prop] = value
  142. // log(prop +' → '+ value.toString())
  143. break;
  144. }
  145. }
  146. }
  147. // 创建元素
  148. const node = document.createElement("DIV")
  149. let nodeHTML = `
  150. <div id="speedCtrl" style="left:${speedData.position.left}px;top:${speedData.position.top}px;">
  151. <div>
  152. <button id="speedText">${speedData.currentSpeed}</button>
  153. <input id="currentSpeed" type="range" min="0.1" max="10" step="0.1" value="${speedData.currentSpeed}"
  154. style="background-size:${speedData.currentSpeed*10}% 100%"
  155. >
  156. </div>
  157. <div>
  158. <span class="usualSpeed" data-value="${speedData.speedArray[0]}" data-index="0">${speedData.speedArray[0]}</span>
  159. <span class="usualSpeed" data-value="${speedData.speedArray[1]}" data-index="1">${speedData.speedArray[1]}</span>
  160. <span class="usualSpeed" data-value="${speedData.speedArray[2]}" data-index="2">${speedData.speedArray[2]}</span>
  161. <span class="usualSpeed" data-value="${speedData.speedArray[3]}" data-index="3">${speedData.speedArray[3]}</span>
  162. <button id="editArray" class="usualfn">编辑</button>
  163. <!--
  164. <button id="expandCtrl" class="usualfn">展开</button>
  165. -->
  166. </div>
  167. </div>
  168. <style>
  169. #speedCtrl{
  170. opacity: 0.1;
  171. width: 50px;
  172. height: 50px;
  173. overflow: hidden;
  174. display: block;
  175. position: fixed;
  176. top: 100px;
  177. left: 50px;
  178. display: grid;
  179. grid-template-rows: 50px 50px;
  180. z-index: 99999999;
  181. background: #ffff0010;
  182. border-radius: 5px;
  183. }
  184. #speedCtrl:hover{
  185. opacity: 1;
  186. background: #ffff0080;
  187. width: auto;
  188. height: auto;
  189. }
  190. #speedCtrl div{
  191. display: flex;
  192. align-items: center;
  193. justify-content: space-evenly;
  194. }
  195. #speedText{
  196. cursor: move;
  197. display: inline-flex;
  198. justify-content: center;
  199. align-items: center;
  200. width: 50px;
  201. height: 50px;
  202. border-radius: 25px;
  203. border: 2px solid #FDC02F;
  204. font-size: 30px;
  205. text-align: center;
  206. font-weight: bold;
  207. background: yellow;
  208. }
  209. .usualfn, .usualSpeed{
  210. display: inline-flex;
  211. justify-content: center;
  212. align-items: center;
  213. width: 50px;
  214. height: 30px;
  215. background: yellow;
  216. border: 2px solid #FDC02F;
  217. border-radius: 5px;
  218. font-family: "微软雅黑", consolas;
  219. }
  220. .usualfn{
  221. cursor: pointer;
  222. }
  223. /*横条样式*/
  224. #speedCtrl input[type='range'] {
  225. -webkit-appearance: none;
  226. /*清除系统默认样式*/
  227. border: 1px solid #FDC02F;
  228. border-radius: 10px;
  229. width: 300px;
  230. background: -webkit-linear-gradient(#ff0, #ff0) no-repeat, #999;
  231. /*设置左边颜色为#ff0,右边颜色为#999*/
  232. /* background-size: 75% 100%; 设置左右宽度比例 */
  233. height: 20px;
  234. /*横条的高度*/
  235. }
  236.  
  237. /*拖动块的样式*/
  238. #speedCtrl input[type=range]::-webkit-slider-thumb {
  239. -webkit-appearance: none;
  240. /*清除系统默认样式*/
  241. height: 40px;
  242. /*拖动块高度*/
  243. width: 40px;
  244. /*拖动块宽度*/
  245. background: #fff;
  246. /*拖动块背景*/
  247. border-radius: 50%;
  248. /*外观设置为圆形*/
  249. border: solid 2px cyan;
  250. /*设置边框*/
  251. cursor: pointer;
  252. }
  253. </style>`;
  254. node.innerHTML = nodeHTML;
  255. document.body.appendChild(node)
  256.  
  257. function move(e){
  258. let moveTarget = document.getElementById("speedCtrl"); //获取目标元素
  259. //算出鼠标相对元素的位置
  260. let disX = e.clientX - moveTarget.offsetLeft;
  261. let disY = e.clientY - moveTarget.offsetTop;
  262. let left, top;
  263. document.onmousemove = (e)=>{ //鼠标按下并移动的事件
  264. //用鼠标的位置减去鼠标相对元素的位置,得到元素的位置
  265. left = e.clientX - disX;
  266. top = e.clientY - disY;
  267. speedState.top = top;
  268. speedState.left = left;
  269. //移动当前元素
  270. moveTarget.style.left = left + 'px';
  271. moveTarget.style.top = top + 'px';
  272. };
  273. document.onmouseup = (e) => {
  274. savePosition(left, top)
  275. document.onmousemove = null;
  276. document.onmouseup = null;
  277. };
  278. }
  279.  
  280. const speedState = new Proxy(speedData, speedyHandler)
  281. const speedText = document.getElementById("speedText")
  282. const currentSpeedElem = document.getElementById("currentSpeed")
  283. const speedArrayElems = querySelectorAll(document, ".usualSpeed")
  284. const editArrayButton = document.getElementById("editArray")
  285.  
  286. speedText.addEventListener('dblclick', e=>{
  287. speedState.currentSpeed = speedState.lastTimeSpeed
  288. })
  289. speedText.addEventListener("mousedown", move)
  290. currentSpeedElem.addEventListener('input', e=>{
  291. speedState.currentSpeed = e.target.value
  292. })
  293. speedArrayElems.forEach(element => {
  294. element.addEventListener('click', e=>{
  295. if(!speedState.editArray){
  296. speedState.currentSpeed = e.target.dataset.value
  297. }
  298. })
  299. element.addEventListener('blur', e=>{
  300. let rate = parseFloat(e.target.textContent)
  301. if(isNumber(rate)){
  302. e.target.dataset.value = rate.toFixed(1)
  303. e.target.textContent = rate.toFixed(1)
  304. }else{
  305. let input = prompt("输入不正确,请输入数字")
  306. if(isNumber(input)){
  307. e.target.dataset.value = rate.toFixed(1)
  308. e.target.textContent = rate.toFixed(1)
  309. }else{
  310. e.target.textContent = e.target.dataset.value
  311. }
  312. }
  313. })
  314. });
  315. editArrayButton.addEventListener("click", e=>{
  316. speedState.editArray = !speedState.editArray
  317. })
  318.  
  319. changeSpeed(GM_getValue('speed'), true)
  320.  
  321.  
  322. }
  323. function isReady(){
  324. let startStamp = new Date().getTime()
  325. window.checkVideoTimer = setInterval(()=>{
  326. let videos = querySelectorAll(document, "video");
  327. let nowStamp = new Date().getTime()
  328. if(videos.length){
  329. clearInterval(checkVideoTimer);
  330. initSpeedy()
  331. }else if((nowStamp - startStamp) > 30000){
  332. clearInterval(checkVideoTimer);
  333. }else{
  334. log('waiting...');
  335. }
  336. },1000);
  337. }
  338.  
  339. isReady();