// ==UserScript== // @name bilibili倍速快捷键【增强】+记忆 // @namespace tonyu_balabala_03e6ea // @version 1.6 // @description bilibili倍速快捷键+记忆(可自定义设置快捷键,自定义一个额外倍速按钮,设置记忆模式) // @author Tony // @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHZpZXdCb3g9IjAgMCAxOCAxOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBjbGFzcz0iemh1emhhbi1pY29uIj48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTMuNzMyNTIgMi42NzA5NEMzLjMzMjI5IDIuMjg0ODQgMy4zMzIyOSAxLjY0MzczIDMuNzMyNTIgMS4yNTc2NEM0LjExMjkxIDAuODkwNjg0IDQuNzE1NTIgMC44OTA2ODQgNS4wOTU5MSAxLjI1NzY0TDcuMjE3MjMgMy4zMDQwM0M3LjI3NzQ5IDMuMzYyMTggNy4zMjg2OSAzLjQyNjEgNy4zNzA4MSAzLjQ5NDA3SDEwLjU3ODlDMTAuNjIxMSAzLjQyNjEgMTAuNjcyMyAzLjM2MjE4IDEwLjczMjUgMy4zMDQwM0wxMi44NTM4IDEuMjU3NjRDMTMuMjM0MiAwLjg5MDY4NCAxMy44MzY4IDAuODkwNjg0IDE0LjIxNzIgMS4yNTc2NEMxNC42MTc1IDEuNjQzNzMgMTQuNjE3NSAyLjI4NDg0IDE0LjIxNzIgMi42NzA5NEwxMy4zNjQgMy40OTQwN0gxNEMxNi4yMDkxIDMuNDk0MDcgMTggNS4yODQ5MyAxOCA3LjQ5NDA3VjEyLjk5OTZDMTggMTUuMjA4NyAxNi4yMDkxIDE2Ljk5OTYgMTQgMTYuOTk5Nkg0QzEuNzkwODYgMTYuOTk5NiAwIDE1LjIwODcgMCAxMi45OTk2VjcuNDk0MDZDMCA1LjI4NDkyIDEuNzkwODYgMy40OTQwNyA0IDMuNDk0MDdINC41ODU3OUwzLjczMjUyIDIuNjcwOTRaTTQgNS40MjM0M0MyLjg5NTQzIDUuNDIzNDMgMiA2LjMxODg2IDIgNy40MjM0M1YxMy4wNzAyQzIgMTQuMTc0OCAyLjg5NTQzIDE1LjA3MDIgNCAxNS4wNzAySDE0QzE1LjEwNDYgMTUuMDcwMiAxNiAxNC4xNzQ4IDE2IDEzLjA3MDJWNy40MjM0M0MxNiA2LjMxODg2IDE1LjEwNDYgNS40MjM0MyAxNCA1LjQyMzQzSDRaTTUgOS4zMTc0N0M1IDguNzY1MTkgNS40NDc3MiA4LjMxNzQ3IDYgOC4zMTc0N0M2LjU1MjI4IDguMzE3NDcgNyA4Ljc2NTE5IDcgOS4zMTc0N1YxMC4yMTE1QzcgMTAuNzYzOCA2LjU1MjI4IDExLjIxMTUgNiAxMS4yMTE1QzUuNDQ3NzIgMTEuMjExNSA1IDEwLjc2MzggNSAxMC4yMTE1VjkuMzE3NDdaTTEyIDguMzE3NDdDMTEuNDQ3NyA4LjMxNzQ3IDExIDguNzY1MTkgMTEgOS4zMTc0N1YxMC4yMTE1QzExIDEwLjc2MzggMTEuNDQ3NyAxMS4yMTE1IDEyIDExLjIxMTVDMTIuNTUyMyAxMS4yMTE1IDEzIDEwLjc2MzggMTMgMTAuMjExNVY5LjMxNzQ3QzEzIDguNzY1MTkgMTIuNTUyMyA4LjMxNzQ3IDEyIDguMzE3NDdaIiBmaWxsPSIjMDBhZWVjIj48L3BhdGg+PC9zdmc+ // @license MIT // @match *://*.bilibili.com/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @downloadURL https://update.greasyfork.icu/scripts/445705/bilibili%E5%80%8D%E9%80%9F%E5%BF%AB%E6%8D%B7%E9%94%AE%E3%80%90%E5%A2%9E%E5%BC%BA%E3%80%91%2B%E8%AE%B0%E5%BF%86.user.js // @updateURL https://update.greasyfork.icu/scripts/445705/bilibili%E5%80%8D%E9%80%9F%E5%BF%AB%E6%8D%B7%E9%94%AE%E3%80%90%E5%A2%9E%E5%BC%BA%E3%80%91%2B%E8%AE%B0%E5%BF%86.meta.js // ==/UserScript== (function() { 'use strict'; let hasObserve = false let isCusSpeed = GM_getValue('tony_is_cus_spd', false) let count = 0 let defaultSpd = 1 let callbackTimer let vdos let spdMemoryMode = GM_getValue('tony_spd_memory_mode', '2') let spdKCSettings = [] let spdKCSettingKeys = ['tony_spd2_KC', 'tony_spd1p5_KC', 'tony_spd1p25_KC', 'tony_spd1_KC', 'tony_spd0p75_KC', 'tony_spd0p5_KC', 'tony_spdcus_KC'] let spdKCSettingDefaultVals = [66, 86, 67, 71, 72, 74, 75] let spdKNames = [] let spdKNameKeys = ['tony_spd2_KC_name', 'tony_spd1p5_KC_name', 'tony_spd1p25_KC_name', 'tony_spd1_KC_name', 'tony_spd0p75_KC_name', 'tony_spd0p5_KC_name', 'tony_spdcus_KC_name'] let spdKNameDefaultVals = ['b', 'v', 'c', 'g', 'h', 'j', 'k'] // spd2_KC_enable, spd1p5_KC_enable, spd1p25_KC_enable, spd1_KC_enable, spd0p75_KC_enable, spd0p5_KC_enable let spdEnableSettings = [] let spdEnableSettingKeys = ['tony_spd2_KC_enable', 'tony_spd1p5_KC_enable', 'tony_spd1p25_KC_enable', 'tony_spd1_KC_enable', 'tony_spd0p75_KC_enable', 'tony_spd0p5_KC_enable', 'tony_spdcus_KC_enable'] let spdEnableSettingDefaultVals = [true, true, false, false, false, false, true] for(let i =0;i0) defaultSpd = bvdos[0].playbackRate function getVdos() { let vdos = document.querySelectorAll(".bpx-player-video-wrap bwp-video") if(vdos.length > 0) return vdos vdos = document.querySelectorAll(".bpx-player-video-wrap video") // if(vdos.length > 0) return vdos // vdos = document.getElementsByTagName('bwp-video') // if(vdos.length > 0) return vdos // vdos = document.getElementsByTagName('video') return vdos } function getSettingList() { let settingList = document.querySelectorAll(".squirtle-setting-panel-wrap>.squirtle-single-select") if(settingList.length==0) settingList = document.querySelectorAll(".bpx-player-ctrl-setting-menu-left .bpx-player-ctrl-setting-mirror") return settingList } function getBeisuList() { let beisuList = document.getElementsByClassName("bilibili-player-video-btn-speed-menu-list") if(beisuList.length==0) { beisuList = document.querySelectorAll(".squirtle-speed-select-list>.squirtle-select-item ") } if(beisuList.length==0) { beisuList = document.querySelectorAll(".edu-player-speed-list>.edu-player-speed-item") } if(beisuList.length==0) { beisuList = document.querySelectorAll(".bpx-player-ctrl-playbackrate-menu>.bpx-player-ctrl-playbackrate-menu-item") } return beisuList } function changeSpeed(beisuList, spd) { switch(spd) { case 2: beisuList[0].click() break case 1.5: beisuList[1].click() break case 1.25: beisuList[2].click() break case 1: beisuList[3].click() break case 0.75: beisuList[4].click() break case 0.5: beisuList[5].click() break case cusSpd: beisuList[6].click() break default: break } } function setPlaybackRate1(beisuList, vdo1) { let intervalCount = 0 let myInterval myInterval = setInterval(() => { intervalCount++ if(intervalCount>40 || vdo1.playbackRate === 1) { let temSpd = spd changeSpeed(beisuList, 1) changeSpeed(beisuList, temSpd) intervalCount = 0 clearInterval(myInterval) } },100) } // video元素有变动及时绑定play监听 function initObserver() { //let vdos = getVdos() let bpxPlayerVideoWraps = document.getElementsByClassName("bpx-player-video-wrap") //let config = {attributes: true, attributeFilter:['src']} let myConfig = {childList:true} function myObserveCallback(mutationsList, observer) { let vdoTagChanged = false for(let i = 0;i0) vdos[0].removeEventListener('play', vdoPlayHandler) vdos = getVdos() if(vdos.length>0) { vdoPlayHandler = getVdoPlayHandler(vdos[0]) vdos[0].addEventListener('play', vdoPlayHandler) vdoPlayHandler() } } } if(bpxPlayerVideoWraps.length>0) { const observer = new MutationObserver(myObserveCallback) observer.observe(bpxPlayerVideoWraps[0], myConfig) hasObserve = true } count++ if(count<=20 && !hasObserve) setTimeout(initObserver, 1000) } initObserver() // 全局记忆 if(spdMemoryMode==='2') defaultSpd = spd else { spd = defaultSpd GM_setValue('tony_spd',defaultSpd) } let cusBeisuMenu let initSpdCnt = 0 let hasAddVdoListener = false let hasAddMenuListener = false let hasConfirmSpd = false let hasAddSettingMenu =false let oldSrc = '' let vdoPlayTimer // 监听视频组件变化 // 视频播放监听回调函数 let vdoPlayHandler function getVdoPlayHandler(vdo) { return function() { clearTimeout(vdoPlayTimer) vdoPlayTimer = setTimeout(function() { //视频开始播放 if(oldSrc !== vdo.src) { let bsList = getBeisuList() if(bsList.length>0) { // 每次初始倍速为1 if(spdMemoryMode === '3') spd = 1 // 默认 当前页面记忆倍速(页面刷新丢失记忆) 或 全局记忆 changeSpeed(bsList, spd) setPlaybackRate1(bsList, vdo) } oldSrc =vdo.src } },100) } } function initSpeedAndListener() { let beisuList = getBeisuList() initSpdCnt++ vdos = getVdos() let settingList = getSettingList() if(spdMemoryMode === '1' || spdMemoryMode === '2' || spdMemoryMode ==='3') { // 尝试持续监听视频play来判断切换 if(vdos.length>0 && !hasAddVdoListener) { vdoPlayHandler = getVdoPlayHandler(vdos[0]) vdos[0].addEventListener('play', vdoPlayHandler) hasAddVdoListener = true } } else if(vdos.length>0 && !hasAddVdoListener) { hasAddVdoListener = true } if(beisuList.length>0 && !hasAddMenuListener) { cusBeisuMenu = beisuList[0].cloneNode(true) let settingMenu = document.createElement('div') let beisuMenuUl = beisuList[0].parentNode settingMenu.className = beisuMenuUl.className settingMenu.setAttribute('style', 'height: 36px;line-height: 36px;cursor: pointer;margin-left:70px;user-select:none;') settingMenu.innerText = '设置' settingMenu.addEventListener('click',showSetting) cusBeisuMenu.setAttribute('data-value', cusSpd) cusBeisuMenu.innerText = cusSpd%1>0?(cusSpd+'x'):(cusSpd+'.0x') beisuList[0].parentNode.appendChild(cusBeisuMenu) beisuMenuUl.parentNode.appendChild(settingMenu) beisuList = getBeisuList() function getMenuClickHandler(i) { return function() { spd = speedList[i] //console.log('spd', spd) GM_setValue('tony_spd',spd) if(i>= originalSpdListLength) { isCusSpeed = true let vdos = getVdos() if(vdos.length>0) vdos[0].playbackRate = spd } else {isCusSpeed = false} GM_setValue('tony_is_cus_spd', isCusSpeed) let bpxPlayerCtrlPlaybackrateResults = document.getElementsByClassName('bpx-player-ctrl-playbackrate-result') if(bpxPlayerCtrlPlaybackrateResults.length>0) bpxPlayerCtrlPlaybackrateResults[0].innerText = spd%1>0?(spd+'x'):(spd===1?'倍速':spd+'.0x') } } for(let i = 0;i0 && !hasAddSettingMenu) { let settingBox = settingList[0].parentNode let spdSettingMenu = document.createElement('div') if(settingList[0].className.indexOf('bui-switch')!==-1) { spdSettingMenu.className = 'bui bui-switch' spdSettingMenu.setAttribute('style', 'height:32px;line-height:32px;width:100%;') let heightBox = settingList[0].parentNode.parentNode heightBox.style.height = parseInt(heightBox.style.height)+32+'px' let outHeightBox = heightBox.parentNode.parentNode outHeightBox.style.height = parseInt(outHeightBox.style.height)+32+'px' } else if(settingList[0].className.indexOf('squirtle-single-select')!==-1) { spdSettingMenu.className = 'squirtle-single-select' } else { spdSettingMenu.className = settingList[0].className } spdSettingMenu.innerText = '倍速设置' spdSettingMenu.addEventListener('click',showSetting) settingBox.appendChild(spdSettingMenu) hasAddSettingMenu = true } if(initSpdCnt<=40 && (!hasAddMenuListener || !hasAddVdoListener || !hasAddSettingMenu)) { setTimeout(initSpeedAndListener,500) } if(hasAddMenuListener && hasAddVdoListener && !hasConfirmSpd) { setPlaybackRate1(beisuList, vdos[0]) hasConfirmSpd = true } } initSpeedAndListener() // 节流标志 let flag = false document.addEventListener('keydown', function(e){ var target = e.target || {}, isInput = ("INPUT" == target.tagName || "TEXTAREA" == target.tagName || "SELECT" == target.tagName || "EMBED" == target.tagName); // 如果是文本输入元素,或者不是一个真实的元素 if(isInput || !target.tagName) return; // 如果是一个虚假的输入元素 if(target && target.getAttribute && target.getAttribute('role') === 'textbox') return; if(flag) return flag = true setTimeout(() => { flag = false },100) let beisuList = getBeisuList() for(let i=0;i16) { cusSpdInput.value = cusSpd cusSpdSettingBtnBox.style.display = 'none' return } cusSpd = temCusSpd GM_setValue('tony_spd_cus', cusSpd) speedList[speedList.length-1] = cusSpd if(cusBeisuMenu) { cusBeisuMenu.setAttribute('data-value', cusSpd) cusBeisuMenu.innerText = cusSpd%1>0?(cusSpd+'x'):(cusSpd+'.0x') if(isCusSpeed && oldCusSpd!==cusSpd) cusBeisuMenu.click() } speedSettingBtns[speedSettingBtns.length-1].innerText = cusSpd if(speedSettingBtns[speedSettingBtns.length-1].style.backgroundColor === 'rgb(50, 130, 236)') speedSettingBtns[speedSettingBtns.length-1].click() cusSpdSettingBtnBox.style.display = 'none' }) cusSpdSettingBtnCancel.innerText = '取消' cusSpdSettingBtnCancel.setAttribute('style', 'margin-right:6px;padding:8px 10px;border:0;color:white;background-color:rgb(243,97,128);border-radius:28px;cursor:pointer;') cusSpdSettingBtnCancel.addEventListener('click', function() { cusSpdInput.value = cusSpd cusSpdSettingBtnBox.style.display = 'none' }) cusSpdSettingBox.appendChild(cusSpdInput) cusSpdSettingBtnBox.appendChild(cusSpdSettingBtnCancel) cusSpdSettingBtnBox.appendChild(cusSpdSettingBtnOK) cusSpdSettingBox.appendChild(cusSpdSettingBtnBox) settingContent.appendChild(cusSpdSettingBox) let KCSettingTitle = document.createElement('div') KCSettingTitle.setAttribute('style', 'margin-top:10px;') KCSettingTitle.innerText = '倍速快捷键设置' settingContent.appendChild(KCSettingTitle) let KCBox = document.createElement('div') KCBox.setAttribute('style', 'margin-top:10px;') let settingBtnBox = document.createElement('div') settingBtnBox.setAttribute('style', 'margin-top:10px;') let speedSettingBtns = [] for(let i = 0; i < speedList.length; i++) { let speedSettingBtn = document.createElement('button') speedSettingBtns.push(speedSettingBtn) speedSettingBtn.setAttribute('style', 'margin:0 3px;padding:8px 10px;min-width:38px;border:0;color:white;background-color:rgb(100,100,100);border-radius:28px;cursor:pointer;') speedSettingBtn.innerText = speedList[i] speedSettingBtn.addEventListener('click', function() { for(let j = 0; j