// ==UserScript==
// @name 哔哩哔哩(B站, bilibili)播放界面和部分操作优化
// @description B站播放器速度自定义(0.25 ~ 3), 支持快捷键(z:正常, x:减少速度, c:增加速度), 鼠标中键切换全屏等
// @namespace bili
// @version 1.5.7
// @author vizo
// @require https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js
// @include *://www.bilibili.com/video*
// @include *://www.bilibili.com/bangumi*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-end
// @noframes
// ==/UserScript==
GM_addStyle(`
html {
overflow-y: scroll;
}
html.hideScroll {
overflow: hidden;
}
body::-webkit-scrollbar {
width: 3px;
}
body::-webkit-scrollbar-corner,
body::-webkit-scrollbar-track {
background-color: #f8f8f8;
}
body::-webkit-scrollbar-thumb {
background: #c5c5c5;
}
#bilibili-player {
position: relative;
}
#spsy_msg {
width: 105px;
height: 42px;
text-align: center;
line-height: 42px;
border-radius: 4px;
background: rgba(255,255,255,.8);
color: #222;
font-size: 16px;
position: absolute;
top: -80px;
right: 0;
bottom: 0;
left: 0;
margin: auto;
z-index: 99999;
pointer-events: none;
display: none;
}
#bl_info_xz {
height: 25px;
line-height: 25px;
font-size: 14px;
padding: 0 5px;
color: #00a1d6;
position: absolute;
top: -25px;
right: 0;
z-index: 2;
}
.bilibili-player-video-btn-volume,
.bilibili-player-video-btn-speed {
opacity: 0.4;
pointer-events: none;
}
.bilibili-player-volumeHint {
display: none !important;
}
`)
const G = {
timer1s: 0,
timer2s: 0,
timer3s: 0,
focusTime: 0,
}
const R = {
timeout(ms = 0) {
return new Promise(resolve => setTimeout(resolve, ms))
},
round(val, n = 2) {
return Number(`${Math.round(`${val}e${n}`)}e-${n}`)
},
floor(val, n = 2) {
return Number(`${Math.floor(`${val}e${n}`)}e-${n}`)
},
loadElement(fnz, timeout = 30e3) {
return new Promise(resolve => {
const iFn = () => {
let el = typeof fnz === 'function' ? fnz() : $(fnz)
if (el.length) {
return resolve(el)
}
setTimeout(iFn, 50)
}
iFn()
setTimeout(() => {
return resolve($(null))
}, timeout)
})
},
watchDom(domOrEl, cb) {
domOrEl = typeof domOrEl === 'string' ? document.querySelector(domOrEl) : domOrEl
return new MutationObserver((mutations, observer) => {
cb(mutations)
})
.observe(domOrEl, {
childList: true,
attributes: true,
characterData: true,
subtree: true,
})
},
async getVideoWrap() {
return (await R.loadElement('#bilibiliPlayer'))[0]
},
async getVideo() {
return (await R.loadElement(`#${(await R.getVideoWrap()).id} video`))[0]
},
async appendMsgLay() {
let wp = await R.getVideoWrap()
let msg = document.getElementById('spsy_msg')
if (!wp.contains(msg)) {
wp.insertAdjacentHTML('beforeend', `<div id="spsy_msg"></div>`)
}
},
async appendAxInfo() {
let wp = await R.getVideoWrap()
let inf = document.getElementById('bl_info_xz')
if (!wp.contains(inf)) {
wp.insertAdjacentHTML('beforeend', `<div id="bl_info_xz"></div>`)
}
},
setAxInfo() {
let inf = $('#bl_info_xz')
let vol = Math.trunc(R.getGMvolume() * 100)
let speed = R.getGMspeed()
if (inf.length) {
speed = speed === 1 ? speed : `<span style="color:#f33;">${speed}</span>`
inf.html(`<span>速度: ${speed} 音量: ${vol}</span>`)
}
},
toggleVideoFullscreen() {
$('.bilibili-player-video-btn-fullscreen')[0].click()
},
setGMspeed(val) {
return GM_setValue('--> bl_player_speed', val)
},
setGMvolume(val) {
GM_setValue('--> bl_player_volume', val)
},
getGMspeed() {
return +GM_getValue('--> bl_player_speed') || 1
},
getGMvolume() {
let vol = GM_getValue('--> bl_player_volume')
return vol !== undefined ? vol : 0.5
},
// 判断是否全屏
isFullScreen() {
return document.isFullScreen || document.mozIsFullScreen || document.webkitIsFullScreen
},
// 显示信息
showSpMsg(msg, type = '速度') {
let mp = $('#spsy_msg')
clearTimeout(G.timer2s)
mp.fadeIn(180)
mp.text(`${type} ${msg}`)
G.timer2s = setTimeout(() => {
mp.fadeOut(350)
}, 800)
},
// 设置播放器播放速度
async setPlayerSpeed(speedVal = R.getGMspeed()) {
let video = await R.getVideo()
video.playbackRate = speedVal
R.setGMspeed(speedVal)
R.setAxInfo()
},
// 设置音量
async setVolume(vol = R.getGMvolume()) {
let video = await R.getVideo()
video.volume = vol
R.setGMvolume(vol)
R.setAxInfo()
},
initVideoCfg() {
clearTimeout(G.timer3s)
G.timer3s = setTimeout(() => {
R.appendMsgLay()
R.appendAxInfo()
R.setPlayerSpeed()
R.setVolume()
}, 10)
},
zTimer() {
R.initVideoCfg()
if (document.hasFocus()) {
G.focusTime = Date.now()
}
setTimeout(R.zTimer, 2000)
},
async regWpMouseEvt() {
let wp = await R.getVideoWrap()
$(wp).on('mouseenter', () => {
$('html').addClass('hideScroll')
})
$(wp).on('mouseleave', () => {
$('html').removeClass('hideScroll')
})
},
}
// 滚轮中键点击(滚轮点击)切换全屏
$('body').on('mousedown', '#bilibili-player', function(e) {
if (e.button === 1) {
e.preventDefault()
R.toggleVideoFullscreen()
}
})
// ctrl和alt按下后短时间内阻止zxc
$('body').on('keydown', function(e) {
if (/up|down/i.test(e.key)) {
e.stopPropagation()
e.preventDefault()
}
})
// 键盘快捷键
$('body').on('keyup', async function(e) {
if (e.target.nodeName !== 'BODY') return
if (/^[zxc]$/.test(e.key) && Date.now() - G.focusTime <= 3000) {
let val = R.getGMspeed()
if (e.key === 'z') {
val = 1
}
if (e.key === 'x') {
val = Math.max(val - 0.25, 0.25)
}
if (e.key === 'c') {
val = Math.min(val + 0.25, 3)
}
R.setPlayerSpeed(val)
R.showSpMsg(val)
}
if (/up|down/i.test(e.key)) {
let vl = R.getGMvolume()
let vol = e.key.includes('Up') ? Math.min(vl + 0.03, 1) : Math.max(vl - 0.03, 0)
R.setVolume(R.floor(vol))
R.showSpMsg(Math.trunc(vol * 100), '音量')
}
})
// 滚轮调节音量
window.addEventListener('wheel', async (e) => {
const wp = await R.getVideoWrap()
const isContains = wp.contains(e.target)
if (!isContains) return
let vol = R.getGMvolume()
if (e.deltaY > 0) {
// 减少音量
vol = Math.max(vol - (e.altKey ? 0.1 : 0.03), 0)
} else {
// 增加音量
vol = Math.min(vol + (e.altKey ? 0.1 : 0.03), 1)
}
vol = R.floor(vol)
R.setVolume(vol)
let pVol = Math.trunc(vol * 100)
R.showSpMsg(pVol, '音量')
})
const initFunc = async () => {
let video = await R.getVideo()
R.zTimer()
video.addEventListener('play', () => {
R.initVideoCfg()
R.regWpMouseEvt()
})
R.regWpMouseEvt()
}
initFunc()