您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
B站自定义倍速播放
// ==UserScript== // @name Bilibili自定义倍速播放 // @namespace http://tampermonkey.net/ // @version 0.8 // @description B站自定义倍速播放 // @updateNote 添加类似 Potplayer 的功能,默认倍速和记忆倍速,方便用户快速切换播放速度;2.修复了某些情况下倍速失效的问题。 // @author 小明 // @license MIT // @match https://www.bilibili.com/* // @icon chrome://favicon/http://www.bilibili.com/ // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @run-at document-end // ==/UserScript== (function () { 'use strict'; const style = ` .video-info { overflow: hidden; text-align: center; box-sizing: border-box; height: 100%; width: 100%; background-color: rgb(241, 242, 243); border-radius: 6px; font-size: 15px; line-height: 30px; margin-bottom: 25px; padding: 10px 10px 0px 10px; pointer-events: all; } .video-info li { width: 30%; float: left; margin-right: 10px; margin-bottom: 10px; list-style: none; } .video-info ul li:hover { background-color: rgb(255, 255, 255); border-radius: 12px; color: #00aeec; cursor:pointer } .video-info ul li:hover span { color: #00aeec; } .video-info span { display: block; width: 100%; } .video-info li span:first-child { color: #222; font-weight: 700; } .video-info li span:last-child { font-size: 12px; color: #18191c; } `; const styleEl = document.createElement('style'); styleEl.textContent = style; document.head.appendChild(styleEl); })(); (function () { 'use strict'; const style = ` #speed { position: absolute; display: flex; justify-content: center; align-items: center; top: 50%; left: 50%; width: 100px; height: 32px; padding: 8px; color: #000; font-size: 20px; border-radius: 7px; background-color: hsla(0, 0%, 100%, .6); transform: translate(-50%, -50%); z-index: 77; visibility: hidden; } `; const styleEl = document.createElement('style'); styleEl.textContent = style; document.head.appendChild(styleEl); })(); (function () { // 隐藏adblock提示 let banner = document.querySelector('.adblock-tips'); if (banner) { // 隐藏横幅元素 banner.style.display = 'none'; } })(); (function () { const SPEED_INTERVAL = 1000 // 倍速步长 let SPEED_DELTA = GM_getValue("SPEED_DELTA", 0.05); // 菜单栏设置项 let speedEnabled = GM_getValue("speedEnabled", true); let timeEnabled = GM_getValue("timeEnabled", true); GM_registerMenuCommand("设置倍速步长", setSpeed); GM_registerMenuCommand("启用/禁用倍速视频功能", toggleSpeed); GM_registerMenuCommand("启用/禁用展示时间信息功能", toggleTime); // 原始播放速度 let originalPlaybackRate = 1 // 是否多p视频 let isMultiPVideo = false //实现保存Z键切换速率 let savedSpeed = 1 let video = document.querySelector('video') || document.querySelector('bwp-video') if (speedEnabled) { // 初始化倍速 let playbackRateStorage = localStorage.getItem('playbackRate') if (playbackRateStorage) { originalPlaybackRate = parseFloat(playbackRateStorage) } // 保存初始倍速 if (video) { video.playbackRate = originalPlaybackRate } } // 对按键监听函数进行节流 const throttleKeydown = throttle((event) => { //拦截器1 if (!speedEnabled) { // 视频功能禁用 return } //拦截器2 if(iSearching()){ return } if (!event.ctrlKey) { let video = document.querySelector('video') || document.querySelector('bwp-video') let keyValue = event.key.toUpperCase() if (keyValue === 'X' && video.playbackRate > SPEED_DELTA) { video.playbackRate = formatNumber(video.playbackRate - SPEED_DELTA) showSpeed(video.playbackRate) } if (keyValue === 'C' && video.playbackRate < 16) { video.playbackRate = formatNumber(video.playbackRate + SPEED_DELTA) showSpeed(video.playbackRate) } if (keyValue === 'Z') { if (video.playbackRate === 1) { video.playbackRate = savedSpeed } else { savedSpeed = video.playbackRate video.playbackRate = 1 } showSpeed(video.playbackRate) } localStorage.setItem('playbackRate', video.playbackRate.toString()) if (isMultiPVideo) { showRemainingDuration(video.playbackRate) } } }) // 对 document 的 keydown 事件进行绑定,调用节流函数 document.addEventListener('keydown', throttleKeydown) // 监听 URL 变化并恢复倍速 let currentUrl = window.location.href setInterval(() => { if (window.location.href !== currentUrl) { currentUrl = window.location.href if (video) { let playbackRateStorage = localStorage.getItem('playbackRate') if (playbackRateStorage) { let playbackRate = parseFloat(playbackRateStorage) if (playbackRate !== video.playbackRate) { if (speedEnabled) { video.playbackRate = playbackRate showSpeed(playbackRate) } if (isMultiPVideo) { showRemainingDuration(video.playbackRate) } } } } } }, 100) let videoTimes = []; // 等待元素加载完成 onReady('.bpx-player-video-area', function () { const div = document.createElement('div'); div.setAttribute('id', 'speed'); div.innerHTML = '<span></span>'; document.querySelector('.bpx-player-video-area').appendChild(div); }, 100) onReady('.list-box .duration', function () { // 兼容性检查 if (checkThirdPartyScript()) { return } isMultiPVideo = true; videoTimes = getVideoTimes(); showRemainingDuration(video.playbackRate) }, 100) onReady('.video-episode-card__info', function () { setTimeout(() => { // 兼容性检查 if (checkThirdPartyScript()) { return } isMultiPVideo = true; videoTimes = getVideoTimes(); showRemainingDuration(video.playbackRate); }, 3000); }, 100) // 小数精度处理 function formatNumber(num) { let decimalNum = Number(num.toString().match(/\.\d+/)); if (isNaN(decimalNum)) { return num; } else if (decimalNum === Math.round(decimalNum)) { return num.toFixed(1); } else { return num.toFixed(2); } } // 设置节流函数 function throttle(fn) { let timer = null return function (...args) { if (!timer) { timer = setTimeout(() => { fn.apply(this, args) timer = null }, 100) } } } // 获取视频播放时间数组 function getVideoTimes() { if (videoTimes.length > 0) { return videoTimes; } let lis = document.querySelectorAll('.list-box .duration'); if (lis.length === 0) { lis = document.querySelectorAll('.video-sections-item .video-episode-card__info-duration') } lis.forEach((currentValue, index) => { const time = currentValue.innerText.replace(/\.\d+/g, ''); videoTimes.push({ timeStr: time, timeSeconds: timeToSeconds(time) }); }); return videoTimes; } function showRemainingDuration(speed = 1) { if (!timeEnabled) { return } let currentspeed = speed let matches = document.querySelector('.cur-page').innerText.match(/\((\d+)\/(\d+)\)/); let start = parseInt(matches[1]); let end = parseInt(matches[2]); let videoData = document.querySelector('#danmukuBox'); let duration = calTime(start, end); // 获取要插入的元素的父元素 let parent = videoData.parentElement; // 查找是否有类名为 "video-info" 的元素 let info = parent.querySelector(".video-info"); // 如果存在,则删除它 if (info) { info.remove(); } const videoInfo = [{ title: '总时长', duration: durationToString(calTime(1, end).total) }, { title: '已看时长', duration: durationToString(calTime(1, start - 1).total) }, { title: '剩余时长', duration: durationToString(calTime(start, end).total) }, { title: '1.5x', duration: durationToString(Math.floor(duration.total / 1.5)) }, { title: '2x', duration: durationToString(Math.floor(duration.total / 2)) }, { title: `${currentspeed}x`, duration: durationToString(Math.floor(duration.total / currentspeed)) }]; let html = ''; videoInfo.forEach(info => { html += `<li> <span>${info.title}</span> <span>${info.duration}</span> </li>`; }); html = `<div> <ul> ${html} </ul> </div>`; videoData.insertAdjacentHTML('afterend', `<div class="video-info">${html}</div>`); } // 根据视频播放时间数组和范围计算时间数据 function calTime(start, end) { const duration = {total: 0, watched: 0, remaining: 0}; const endIndex = Math.min(videoTimes.length, end); for (let i = start - 1; i < endIndex; i++) { const data = videoTimes[i]; if (i < end - 1) { duration.watched += data.timeSeconds; } else { duration.remaining += data.timeSeconds; } duration.total += data.timeSeconds; } return duration; } // 秒转hh:mm:ss function durationToString(duration) { const h = parseInt(duration / 3600); const m = parseInt(duration / 60) % 60; const s = duration % 60; if (h > 0) { return `${h}h ${m}min ${s}s`; } else { return `${m}min ${s}s`; } } // 等待元素加载完成函数 function onReady(selector, func, times = -1, interval = 20) { let intervalId = setInterval(() => { if (times === 0) { clearInterval(intervalId) } else { times -= 1 } if (document.querySelector(selector)) { clearInterval(intervalId) func() } }, interval) } // 显示速度函数 function showSpeed(speed, index = 1) { let speedDiv = document.querySelector(`#speed`); if (!speedDiv) { const div = document.createElement('div'); div.setAttribute('id', 'speed'); div.innerHTML = '<span></span>'; document.querySelector('.bpx-player-video-area').appendChild(div); speedDiv = div; } let speedSpan = speedDiv.querySelector('span') if (index == 1) { speedSpan.innerHTML = `${speed} X` } else { speedSpan.innerHTML = `${speed}` } speedDiv.style.visibility = 'visible' clearTimeout(window.speedTimer) window.speedTimer = setTimeout(() => { speedDiv.style.visibility = 'hidden' }, SPEED_INTERVAL) } // 检测第三方倍速插件 function checkThirdPartyScript() { //没有开倍速就不用检测了 if (!speedEnabled) { return false } if (document.querySelector(".html_player_enhance_tips")) { document.querySelector('#danmukuBox').insertAdjacentHTML('afterend', `<div class="video-info"><div> 请禁用第三方倍速脚本<br>- 🚀Bilibili 倍速与多P剩余时长显示增强脚本 - </div></div>`); return true; } else { return false; } } // 将时间字符串转换为秒数 function timeToSeconds(time) { const timeArr = time.split(':'); let timeSeconds = 0; if (timeArr.length === 3) { timeSeconds += Number(timeArr[0]) * 60 * 60; timeSeconds += Number(timeArr[1]) * 60; timeSeconds += Number(timeArr[2]); } else { timeSeconds += Number(timeArr[0]) * 60; timeSeconds += Number(timeArr[1]); } return timeSeconds; } // 菜单栏切换倍速功能状态 function toggleSpeed() { speedEnabled = !speedEnabled; GM_setValue("speedEnabled", speedEnabled); if (speedEnabled) { showSpeed("倍速:启用", 2) } else { showSpeed("倍速:禁用", 2) } } // 菜单栏切换时间展示功能状态 function toggleTime() { timeEnabled = !timeEnabled; GM_setValue("timeEnabled", timeEnabled); if (timeEnabled) { showSpeed("展示:启用", 2) showRemainingDuration(video.playbackRate); } else { showSpeed("展示:禁用", 2) let info = document.querySelector('#danmukuBox').parentElement.querySelector(".video-info"); // 如果存在,则删除它 if (info) { info.remove(); } } } // 菜单栏设置倍速步长 function setSpeed() { var input = prompt("请输入倍速步长(默认0.05):", SPEED_DELTA); if (input === null) { return; } if (isNaN(input) || input === "") { alert("请输入数字!"); } else { if (Number(input) > 0) { SPEED_DELTA = Number(input); GM_setValue("SPEED_DELTA", SPEED_DELTA); } } } //在搜索栏输入文字时不调整倍速 function iSearching(){ let s1 = false; let s2 = false; //焦点在搜索框 if(document.getElementById('nav-searchform').length>0){ s1 = document.getElementById('nav-searchform').classList.contains('is-actived'); } //焦点在评论区 if(document.getElementsByClassName('reply-box-textarea').length>0){ s2 = document.getElementsByClassName('reply-box-textarea')[0].classList.contains('focus'); } let s = s1 || s2; return s; } })(); (function () { //标题简洁 setTimeout(function(){ ptile(); },6000) })(); //--------------标题简洁 函数--------------------start //标题简洁风 function ptile(){ if(document.getElementsByClassName('base-video-sections-v1') == null){ return; } console.info('---ptile---'); let arr = document.querySelectorAll('.video-episode-card .video-episode-card__info-title'); let prefix = findPrefix(); for(var i = 0;i<arr.length;i++){ var str = arr[i].innerText; str = str.replace(prefix,''); arr[i].innerText = str; } } //寻找标题公共前缀 function findPrefix(){ var prefix = ''; // NodeList 不是一个数组,是一个类似数组的对象.可以使用 Array.from() 将其转换为数组 var liArr = document.querySelectorAll('.video-episode-card .video-episode-card__info-title'); liArr = Array.from(liArr); var arr = liArr.map( (item, index) => { return item.title }) //console.log("a标签的title集合", arr) if(arr.length>=3){ //随机采样 var index1 = getRndInteger(0,arr.length); var index2 = getRndInteger(0,arr.length); var index3 = getRndInteger(0,arr.length); var s1s2 = [arr[index1],arr[index2]]; var s2s3 = [arr[index2],arr[index3]]; console.info(s1s2); console.info(s2s3); var s1s2_Pre = longestCommonPrefix(s1s2); var s2s3_Pre = longestCommonPrefix(s2s3); if(s1s2_Pre == s2s3_Pre){ prefix = s1s2_Pre; } } return prefix; } //JavaScript 最长公共前缀 function longestCommonPrefix(strs) { if(strs.length == 0) return ""; let ans = strs[0]; for(let i =1;i<strs.length;i++) { let j=0; for(;j<ans.length && j < strs[i].length;j++) { if(ans[j] != strs[i][j]) break; } ans = ans.substr(0, j); if(ans === "") return ans; } return ans; }; //返回 min(包含)~ max(不包含)之间的数字 function getRndInteger(min, max) { return Math.floor(Math.random() * (max - min) ) + min; } //--------------标题简洁 函数--------------------end
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址