// ==UserScript== // @name bilibili倍速快捷键【增强】+记忆 // @namespace tonyu_balabala_03e6ea // @version 1.5 // @description bilibili倍速快捷键+记忆(可自定义设置快捷键,自定义一个额外倍速按钮,设置记忆模式) // @author Tony // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAoVQTFRFAAAAfov/f4v/f4z/f4z/f4z/f4z/f4v/fYr/fov/f4z/f4z/f4z/f4z/f4z/foz/fYv/e4n/f4z/f4z/f4z/fov/eor/for/f4z/f4z/f4z/foz/f4v/eYj/eon/fov/foz/f4z/f4z/f4z/foz/f4z/f4z/fov/d4j/fov/f4z/f4z/fov/foz/f4z/fov/fIz/UWD8UWD8UWD8UWD8q7D/q7D/q7D/fYv/foz/fov/fIn/f4z/QID/UWD8UWD8UWD8q7D/q7D/q7D/q7D/Zmb/f4z/f4z/eYz/f4z/fov/UWD8q7D/q7D/q7D/VYD/f4z/f4z/f4r/f4z/fIv/q7D/for/f4z/fYr/f4z/fov/q7D/q7D/fov/foz/f4z/aoD/q7D/cID/f4z/fYv/f4v/q7D/q7D/q7D/f4z/f4z/fov/UWD8UWD8q7D/q7D/q7D/q7D/q7D/f4v/f4z/fov/UWD8UWD8UWD8f4z/f4z/f4z/e4f/UWD8UWD8d4n/f4z/d4b/UWD8eIf/f4z/f4z/UWD8UWD8UWD8UWD8f4v/f4z/foz/fov/foz/f4z/fYv/f4z/f4z/fIv/f4z/eIf/eYb/f4z/f4z/fov/fov/f4z/foz/f4z/fIr/fIz/f4z/f4z/f4z/cXH/YID/f4z/foz/fYj/f4z/UWD8UWD8f4z/fIn/for/foz/fIz/UWD8UWD8foz/f4z/for/f4v/fov/bW3/cXH/f4z/f4z/foz/f4z/f4z/fov/foz/eYj/eon/foz/f4z/f4z/fov/eIf/f4z/f4z/f4z/f4v/e4T/for/foz/f4z/f4z/f4z/f4v/for/f4v/f4z/f4v/fov/VQ1gSwAAANd0Uk5TAB9fg5SSgVoXFY3Z/v/RgQ4FjPXwewMh1MloNxUEBhg7b9XFLujMOgFG29weH+eFBhjl2xAeRAEJnRQE0ABM/zOU/9E9AIq5A4mDTZn7awCjYxfLBl4J3g6OOfcoV2XdALEB2iBjhP4ckFkxUjcJXGZhCEuCEtS9UBxWkwTk1wdiBcgIYYDBojYLIFVYNE86Gm2aEdYBAuXAgURmWe8JDeZ3nwABulQDw0sypQIWpQnYyrjKDh5QAAHIFBW7kUMbBwhLmeMOAmDt5FIBDGPG/bxZCD9YPBCTjRZGAAACCklEQVR4nJWT+V9MURjGX1FMzaNoUZaULJkSaSRbJzdDSZRE2UJ2lZ1kC9FiT9bKkrKVXUJR1rLv/p7OPXfmujN3/OD55Tzv+3w/n3Pez+c9RP+jLi5du7m6de/hPDW4exghZOzp6aWLe/X2hkY+vn72eR9/3g3o26//gMCBQcGDeBFi0OaDhwBDh4XaytDhJiAs/G8+IgIYOUp2kaOjzGOix1LMOMA03pZPmAgETxI2lrG4yVL8FLJMBaYlWIFEYLqSUxIHZkhS8kyalQKkKs3Z3kibQ3aAJKXT3HnwyRDNTGA+OQILFtIiYLFoZiFkiQ6QltKybKTJveXACtICKwWwajWtAeRB1gLrVCCHsdy89Ru4Nm6izUAQ720BtlJkklDONsa25yveTDuAAg7sBHbRbqbTHtoLFHLAFdhHUXpgPx0AijhwUH6KOU4o9xBjxSWKL6Uy4DAHjgBH1UfmM1aiFseA4/w4AZy0G1MtymE8JZ8VqDjtDDhzFueEOQ9ccAZUAlXCeFUj+6IeuBSByzGKvQLUXHUEauuAa1b2+g0g0SLsTcZuCWPh61DfYLvtdhZw567szPful4prHwAPH6nzUKO8pI+bbOWTIr6kT5+RRs0tfNNNz1+0tr0Mf/X6DS/q35Kd3rV3aD/O+w8fyVGfPn+xfr2v377/0MVChp+/UgsLfv9xnv5Dnauuj5T4NeYmAAAAAElFTkSuQmCC // @license MIT // @match *://*.bilibili.com/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @downloadURL none // ==/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) { // 有时候直接点击当前倍速按钮并没有效果,通过先点击1倍速按钮,再点击其他倍速按钮来实现。 let temSpd = spd changeSpeed(beisuList, 1) changeSpeed(beisuList, temSpd) intervalCount = 0 clearInterval(myInterval) } },100) } 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) { // 不知什么原因,部分场景下设置为其他倍速,比如2倍速,切换视频后显示依然是2倍速,但是视频确实1倍速,通过再次点击2倍速来纠正 // 每次初始倍速为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