// ==UserScript==
// @name 哔哩哔哩(bilibili.com)播放页调整
// @license GPL-3.0 License
// @namespace https://greasyfork.org/zh-CN/scripts/415804-bilibili%E6%92%AD%E6%94%BE%E9%A1%B5%E8%B0%83%E6%95%B4-%E8%87%AA%E7%94%A8
// @version 0.39
// @description 1.自动定位到播放器(进入播放页,可自动定位到播放器,可设置偏移量及是否在点击主播放器时定位);2.可设置是否自动选择最高画质;3.可设置播放器默认模式;
// @author QIAN
// @match *://*.bilibili.com/video/*
// @match *://*.bilibili.com/bangumi/play/*
// @match *://*.bilibili.com/list/*
// @run-at document-start
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require https://unpkg.com/[email protected]/dist/sweetalert2.min.js
// @resource swalStyle https://unpkg.com/[email protected]/dist/sweetalert2.min.css
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_getResourceText
// @grant GM.info
// @supportURL https://github.com/QIUZAIYOU/Bilibili-VideoPage-Adjustment
// @homepageURL https://github.com/QIUZAIYOU/Bilibili-VideoPage-Adjustment
// @icon https://www.bilibili.com/favicon.ico?v=1
// ==/UserScript==
$(() => {
'use strict'
let {
currentUrl,
theMainFunctionRunningTimes,
thePrepFunctionRunningTimes,
autoSelectScreenModeTimes,
autoCancelMuteTimes,
webfullUnlockTimes,
insertGoToCommentsButtonTimes,
autoSelectVideoHighestQualityTimes,
functionExecutionsTimes = 0,
} = {
currentUrl: window.location.href,
theMainFunctionRunningTimes: 0,
thePrepFunctionRunningTimes: 0,
autoSelectScreenModeTimes: 0,
autoCancelMuteTimes: 0,
webfullUnlockTimes: 0,
insertGoToCommentsButtonTimes: 0,
autoSelectVideoHighestQualityTimes: 0,
}
const {
getValue,
setValue,
sleep,
addStyle,
historyListener,
checkBrowserHistory,
throttle,
getClientHeight,
checkElementExistence,
isDocumentHidden,
isLogin,
logger,
checkPageReadyState,
pageReload,
scrollToPlayer
} = {
getValue (name) {
return GM_getValue(name)
},
setValue (name, value) {
GM_setValue(name, value)
},
sleep (time) {
return new Promise(resolve => setTimeout(resolve, time))
},
addStyle (id, tag, css) {
tag = tag || 'style'
const doc = document
const styleDom = doc.getElementById(id)
if (styleDom) return
const style = doc.createElement(tag)
style.rel = 'stylesheet'
style.id = id
tag === 'style' ? (style.innerHTML = css) : (style.href = css)
document.head.appendChild(style)
},
historyListener () {
class Dep {
constructor(name) {
this.id = new Date()
this.subs = []
}
defined () {
Dep.watch.add(this)
}
notify () {
this.subs.forEach((e, i) => {
if (typeof e.update === 'function') {
try {
e.update.apply(e)
} catch (err) {
console.warr(err)
}
}
})
}
}
Dep.watch = null
class Watch {
constructor(name, fn) {
this.name = name
this.id = new Date()
this.callBack = fn
}
add (dep) {
dep.subs.push(this)
}
update () {
var cb = this.callBack
cb(this.name)
}
}
var addHistoryMethod = (function () {
var historyDep = new Dep()
return function (name) {
if (name === 'historychange') {
return function (name, fn) {
var event = new Watch(name, fn)
Dep.watch = event
historyDep.defined()
Dep.watch = null
}
} else if (name === 'pushState' || name === 'replaceState') {
var method = history[name]
return function () {
method.apply(history, arguments)
historyDep.notify()
// logger.info("访问历史|变化")
}
}
}
})()
window.addHistoryListener = addHistoryMethod('historychange')
history.pushState = addHistoryMethod('pushState')
history.replaceState = addHistoryMethod('replaceState')
window.addHistoryListener('history', function () {
const throttleAutoLocation = throttle(m.autoLocation, 500)
throttleAutoLocation()
})
},
checkBrowserHistory () {
window.addEventListener('popstate', () => {
m.autoLocation()
})
},
throttle (func, delay) {
let wait = false
return (...args) => {
if (wait) {
return
}
func(...args)
wait = true
setTimeout(() => {
wait = false
}, delay)
}
},
getClientHeight () {
const bodyHeight = document.body.clientHeight || 0
const docHeight = document.documentElement.clientHeight || 0
return bodyHeight < docHeight ? bodyHeight : docHeight
},
// 检查指定HTML元素是否存在
checkElementExistence (selector, maxAttempts, interval) {
// functionExecutionsTimes += 1
// const funName = (new Error()).stack.split("\n")[2].trim().split(" ")[1].replace('Object.', '')
// logger.debug(`(调用:${functionExecutionsTimes}) ${funName} -> ${selector}`)
return new Promise(resolve => {
let attempts = 0
const intervalId = setInterval(() => {
attempts++
// logger.debug(`(尝试:${attempts}) -> ${selector}`)
const element = $(selector)
if (element.length) {
clearInterval(intervalId)
resolve(true)
} else if (attempts === maxAttempts) {
clearInterval(intervalId)
resolve(false)
}
}, interval)
})
},
isDocumentHidden () {
const visibilityChangeEventNames = ['visibilitychange', 'mozvisibilitychange', 'webkitvisibilitychange', 'msvisibilitychange']
const documentHiddenPropertyName = visibilityChangeEventNames.find(name => name in document) || 'onfocusin' in document || 'onpageshow' in window ? 'hidden' : null
if (documentHiddenPropertyName !== null) {
const isHidden = () => document[documentHiddenPropertyName]
const onChange = () => isHidden()
// 添加各种事件监听器
visibilityChangeEventNames.forEach(eventName => document.addEventListener(eventName, onChange))
window.addEventListener('focus', onChange)
window.addEventListener('blur', onChange)
window.addEventListener('pageshow', onChange)
window.addEventListener('pagehide', onChange)
document.onfocusin = document.onfocusout = onChange
return isHidden()
}
// 如果无法判断是否隐藏,则返回undefined
return undefined
},
isLogin () {
return Boolean(document.cookie.replace(new RegExp(String.raw`(?:(?:^|.*;\s*)bili_jct\s*=\s*([^;]*).*$)|^.*$`), '$1') || null)
},
logger: {
info (content) {
console.info('%c播放页调整', 'color:white;background:#006aff;padding:2px;border-radius:2px', content)
},
warn (content) {
console.warn('%c播放页调整', 'color:white;background:#ff6d00;padding:2px;border-radius:2px', content)
},
error (content) {
console.error('%c播放页调整', 'color:white;background:#f33;padding:2px;border-radius:2px', content)
},
debug (content) {
console.info('%c播放页调整(调试)', 'color:white;background:#cc00ff;padding:2px;border-radius:2px', content)
},
},
checkPageReadyState (state) {
return new Promise((resolve) => {
const timer = setInterval(() => {
if (document.readyState === state) {
clearInterval(timer)
resolve(true)
}
}, 100)
})
},
pageReload () {
if (auto_reload) location.reload(true)
},
scrollToPlayer (offset) {
$('html,body').scrollTop(offset)
}
}
const {
is_vip,
player_type,
offset_top,
auto_locate,
auto_locate_video,
auto_locate_bangumi,
click_player_auto_locate,
player_offset_top,
current_screen_mode,
selected_screen_mode,
auto_select_video_highest_quality,
contain_quality_4k,
contain_quality_8k,
webfull_unlock,
auto_reload
} = {
is_vip: getValue('is_vip'),
player_type: getValue('player_type'),
offset_top: Math.trunc(getValue('offset_top')),
auto_locate: getValue('auto_locate'),
auto_locate_video: getValue('auto_locate_video'),
auto_locate_bangumi: getValue('auto_locate_bangumi'),
click_player_auto_locate: getValue('click_player_auto_locate'),
player_offset_top: Math.trunc(getValue('player_offset_top')),
current_screen_mode: getValue('current_screen_mode'),
selected_screen_mode: getValue('selected_screen_mode'),
auto_select_video_highest_quality: getValue('auto_select_video_highest_quality'),
contain_quality_4k: getValue('contain_quality_4k'),
contain_quality_8k: getValue('contain_quality_8k'),
webfull_unlock: getValue('webfull_unlock'),
auto_reload: getValue('auto_reload')
}
const m = {
// 初始化设置参数
initValue () {
const value = [{
name: 'is_vip',
value: false,
}, {
name: 'player_type',
value: 'video',
}, {
name: 'offset_top',
value: 7,
}, {
name: 'player_offset_top',
value: 160,
}, {
name: 'auto_locate',
value: true,
}, {
name: 'auto_locate_video',
value: true,
}, {
name: 'auto_locate_bangumi',
value: true,
}, {
name: 'click_player_auto_locate',
value: true,
}, {
name: 'current_screen_mode',
value: 'normal',
}, {
name: 'selected_screen_mode',
value: 'wide',
}, {
name: 'auto_select_video_highest_quality',
value: true,
}, {
name: 'contain_quality_4k',
value: false,
}, {
name: 'contain_quality_8k',
value: false,
}, {
name: 'webfull_unlock',
value: false,
}, {
name: 'auto_reload',
value: false,
}]
value.forEach(v => {
if (getValue(v.name) === undefined) {
setValue(v.name, v.value)
}
})
},
// 检查视频资源是否加载完毕并处于可播放状态
async checkVideoCanPlayThrough () {
const BwpVideoPlayerExists = await checkElementExistence('bwp-video', 10, 10)
// logger.debug(`bwp-video|${BwpVideoPlayerExists?'存在':'不存在'}`)
if (BwpVideoPlayerExists) {
return new Promise(resolve => {
resolve(true)
})
}
const $video = $('#bilibili-player video')
const videoReadyState = $video[0].readyState
// logger.debug(`视频资源|${videoReadyState>=4?'可播放':'不可播放'}`
if (videoReadyState >= 4) {
return new Promise(resolve => {
resolve(true)
})
} else {
return new Promise(resolve => {
const checkTimeout = setTimeout(() => {
// logger.error('视频资源|脚本检测失败|重载页面')
pageReload()
resolve(false)
}, 7000)
$video.on('canplaythrough', () => {
// logger.info("视频资源加载|成功")
let attempts = 100
const timer = setInterval(() => {
const isHidden = $('#bilibili-player .bpx-player-container').attr('data-ctrl-hidden')
if (isHidden === 'false') {
clearInterval(timer)
clearTimeout(checkTimeout)
// logger.info(`视频可播放`)
// logger.info(`控制条|出现(hidden:${isHidden})`)
resolve(true)
} else if (attempts <= 0) {
clearInterval(timer)
clearTimeout(checkTimeout)
// logger.error("控制条|检查失败")
resolve(false)
}
// logger.info("控制条|检查中")
attempts--
}, 100)
})
})
}
},
// 获取当前视频类型(video/bangumi)
getCurrentPlayerType () {
const isVideo = currentUrl.includes('www.bilibili.com/video') || currentUrl.includes('www.bilibili.com/list/')
const isBangumi = currentUrl.includes('www.bilibili.com/bangumi')
setValue('player_type', isVideo ? 'video' : isBangumi && 'bangumi')
},
// 获取当前屏幕模式(normal/wide/web/full)
async getCurrentScreenMode () {
const exists = await checkElementExistence('#bilibili-player .bpx-player-container', 10, 100)
if (exists) {
const screenMode = $('#bilibili-player .bpx-player-container').attr('data-screen')
return Promise.resolve(screenMode)
} else return Promise.resolve(false)
},
// 监听屏幕模式变化(normal/wide/web/full)
watchScreenModeChange () {
const screenModObserver = new MutationObserver(mutations => {
const playerDataScreen = $('#bilibili-player .bpx-player-container').attr('data-screen')
setValue('current_screen_mode', playerDataScreen)
})
screenModObserver.observe($('#bilibili-player .bpx-player-container')[0], {
attributes: true,
attributeFilter: ['data-screen'],
})
},
// 判断自动切换屏幕模式是否切换成功
async checkScreenModeSuccess (expect_mode) {
const current_screen_mode = await this.getCurrentScreenMode()
const player_data_screen = $('#bilibili-player .bpx-player-container').attr('data-screen')
const equal = new Set([
expect_mode,
selected_screen_mode,
current_screen_mode,
player_data_screen,
]).size === 1
return Promise.resolve(equal)
},
// 自动选择屏幕模式
async autoSelectScreenMode () {
const current_screen_mode = await this.getCurrentScreenMode()
if (current_screen_mode === 'wide') return { done: true, mode: selected_screen_mode }
if (current_screen_mode === 'web') return { done: true, mode: selected_screen_mode }
autoSelectScreenModeTimes++
if (autoSelectScreenModeTimes === 1) {
const wideEnterBtn = document.querySelector('.bpx-player-ctrl-wide-enter')
const webEnterBtn = document.querySelector('.bpx-player-ctrl-web-enter')
const selectModeBtn = selected_screen_mode === 'wide' ? wideEnterBtn : webEnterBtn
const expect_mode = selected_screen_mode === 'wide' ? 'wide' : 'web'
let attempts = 50
selectModeBtn.click()
const checkScreenMode = async (expect_mode) => {
const success = await this.checkScreenModeSuccess(expect_mode)
if (success) {
clearInterval(checkScreenModeInterval)
setValue('current_screen_mode', selected_screen_mode)
return {
done: true,
mode: selected_screen_mode
}
} else {
await sleep(1000)
selectModeBtn.click()
logger.warn('自动选择屏幕模式失败正在重试')
attempts--
if (attempts === 0) {
clearInterval(checkScreenModeInterval)
pageReload()
}
}
}
let checkScreenModeInterval = setInterval(checkScreenMode, 100, expect_mode)
return new Promise(resolve => {
checkScreenMode(expect_mode).then(result => {
resolve(result)
})
})
}
},
// 网页全屏解锁
fixedWebfullUnlockStyle () {
webfullUnlockTimes++
async function resetPlayerLayout () {
$('body').css({
'padding-top': 0,
position: 'auto',
})
$('#playerWrap').css('display', 'block')
$('#bilibili-player').css({
height: 'auto',
position: 'unset',
})
$('#playerWrap').append($('#bilibili-player'))
$('.float-nav-exp .mini').css('display', '')
// 临时设置默认屏幕模式为宽屏用以触发执行自动定位至播放器,定位完后再重新改为网页全屏
setValue('selected_screen_mode', 'wide')
const playerDataScreen = await m.getCurrentScreenMode()
if (playerDataScreen !== 'full') {
m.autoLocation()
}
setValue('selected_screen_mode', 'web')
}
if (webfullUnlockTimes === 1) {
const clientHeight = getClientHeight()
$('body.webscreen-fix').css({
'padding-top': clientHeight,
position: 'unset',
})
$('#bilibili-player.mode-webscreen').css({
height: clientHeight,
position: 'absolute',
})
$('#app').prepend($('#bilibili-player.mode-webscreen'))
$('#playerWrap').css('display', 'none')
logger.info('网页全屏解锁|成功')
setValue('current_screen_mode', 'web')
this.insertGoToCommentsButton()
// 退出网页全屏
$('.bpx-player-ctrl-btn-icon.bpx-player-ctrl-web-leave').click(function () {
resetPlayerLayout()
})
// 再次进入网页全屏
$('.bpx-player-ctrl-btn-icon.bpx-player-ctrl-web-enter').click(function () {
$('body').css({
'padding-top': clientHeight,
position: 'unset',
})
$('#bilibili-player').css({
height: clientHeight,
position: 'absolute',
})
$('#app').prepend($('#bilibili-player'))
$('#playerWrap').css('display', 'none')
$('.float-nav-exp .mini').css('display', 'none')
$('html,body').scrollTop(0)
})
// 进入退出全屏
$('.bpx-player-ctrl-btn.bpx-player-ctrl-full').click(function () {
resetPlayerLayout()
})
// 进入宽屏
$('.bpx-player-ctrl-btn-icon.bpx-player-ctrl-wide-enter').click(function () {
resetPlayerLayout()
})
}
},
// 插入跳转评论按钮
insertGoToCommentsButton () {
insertGoToCommentsButtonTimes++
if (player_type === 'video' && webfull_unlock && insertGoToCommentsButtonTimes === 1) {
const goToCommentsBtnHtml = '<div class="bpx-player-ctrl-btn bpx-player-ctrl-comment" role="button" aria-label="前往评论" tabindex="0"><div id="goToComments" class="bpx-player-ctrl-btn-icon"><span class="bpx-common-svg-icon"><svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="88" height="88" preserveAspectRatio="xMidYMid meet" style="width: 100%; height: 100%; transform: translate3d(0px, 0px, 0px);"><path d="M512 85.333c235.637 0 426.667 191.03 426.667 426.667S747.637 938.667 512 938.667a424.779 424.779 0 0 1-219.125-60.502A2786.56 2786.56 0 0 0 272.82 866.4l-104.405 28.48c-23.893 6.507-45.803-15.413-39.285-39.296l28.437-104.288c-11.008-18.688-18.219-31.221-21.803-37.91A424.885 424.885 0 0 1 85.333 512c0-235.637 191.03-426.667 426.667-426.667zm-102.219 549.76a32 32 0 1 0-40.917 49.216A223.179 223.179 0 0 0 512 736c52.97 0 103.19-18.485 143.104-51.67a32 32 0 1 0-40.907-49.215A159.19 159.19 0 0 1 512 672a159.19 159.19 0 0 1-102.219-36.907z" fill="#currentColor"/></svg></span></div></div>'
$('.bpx-player-control-bottom-right').append(goToCommentsBtnHtml)
$('#goToComments').on('click', function (event) {
event.stopPropagation()
$('body,html').scrollTop($('#comment').offset().top - 10)
logger.info('到达评论区')
})
}
},
// 添加返回播放器按钮
async insertBackToPlayerButton () {
const playerDataScreen = await this.getCurrentScreenMode()
if (player_type === 'video') {
const locateButtonHtml = '<div class="fixed-sidenav-storage-item locate" title="定位至播放器">\n<svg t="1643419779790" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1775" width="200" height="200" style="width: 50%;height: 100%;fill: currentColor;"><path d="M512 352c-88.008 0-160.002 72-160.002 160 0 88.008 71.994 160 160.002 160 88.01 0 159.998-71.992 159.998-160 0-88-71.988-160-159.998-160z m381.876 117.334c-19.21-177.062-162.148-320-339.21-339.198V64h-85.332v66.134c-177.062 19.198-320 162.136-339.208 339.198H64v85.334h66.124c19.208 177.062 162.144 320 339.208 339.208V960h85.332v-66.124c177.062-19.208 320-162.146 339.21-339.208H960v-85.334h-66.124zM512 810.666c-164.274 0-298.668-134.396-298.668-298.666 0-164.272 134.394-298.666 298.668-298.666 164.27 0 298.664 134.396 298.664 298.666S676.27 810.666 512 810.666z" p-id="1776"></path></svg></div>'
const floatNav = $('.fixed-sidenav-storage .back-to-top-wrap')
// $('.fixed-sidenav-storage').css('bottom', '274px')
const dataV = floatNav[0].attributes[1].name
const locateButtonHtmlDataV = locateButtonHtml.replace('title="定位至播放器"', `title="定位至播放器" ${dataV}`)
floatNav.prepend(locateButtonHtmlDataV)
const locateButton = $('.storable-items .fixed-sidenav-storage-item.locate')
locateButton.not(':first-child').remove()
floatNav.on('click', '.locate', function () {
$('html,body').scrollTop(playerDataScreen !== 'web' ? player_offset_top - offset_top : 0)
})
}
if (player_type === 'bangumi') {
const floatNav = $('[class*="navTools_floatNavExp"] [class*="navTools_navMenu"]')
const floatNavMenuItemClass = floatNav.children('a').children('div').attr('class').split(' ')[0]
const locateButtonHtml = `<div class="${floatNavMenuItemClass} locate" style="height:40px;padding:0" title="定位至播放器">\n<svg t="1643419779790" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1775" width="200" height="200" style="width: 50%;height: 100%;fill: currentColor;"><path d="M512 352c-88.008 0-160.002 72-160.002 160 0 88.008 71.994 160 160.002 160 88.01 0 159.998-71.992 159.998-160 0-88-71.988-160-159.998-160z m381.876 117.334c-19.21-177.062-162.148-320-339.21-339.198V64h-85.332v66.134c-177.062 19.198-320 162.136-339.208 339.198H64v85.334h66.124c19.208 177.062 162.144 320 339.208 339.208V960h85.332v-66.124c177.062-19.208 320-162.146 339.21-339.208H960v-85.334h-66.124zM512 810.666c-164.274 0-298.668-134.396-298.668-298.666 0-164.272 134.394-298.666 298.668-298.666 164.27 0 298.664 134.396 298.664 298.666S676.27 810.666 512 810.666z" p-id="1776"></path></svg></div>`
floatNav.prepend(locateButtonHtml)
const locateButton = $(`${floatNavMenuItemClass}.locate`)
locateButton.not(':first-child').remove()
floatNav.on('click', '.locate', function () {
$('html,body').scrollTop(playerDataScreen !== 'web' ? player_offset_top - offset_top : 0)
})
}
},
// 自动定位至播放器
autoLocation () {
const $player = $('#bilibili-player')
const player_offset_top = Math.trunc($player.offset().top)
setValue('player_offset_top', player_offset_top)
return new Promise(resolve => {
const isAutoLocate = auto_locate && ((!auto_locate_video && !auto_locate_bangumi) || (auto_locate_video && player_type === 'video') || (auto_locate_bangumi && player_type === 'bangumi'))
if (!isAutoLocate || getValue('selected_screen_mode') === 'web') {
resolve(false)
// 未开启功能或模式为网页全屏时直接返回,防止代码继续执行进入死循环
return
}
scrollToPlayer(player_offset_top - offset_top)
const applyAutoLocationInterval = setInterval(() => {
scrollToPlayer(player_offset_top - offset_top)
logger.warn(`自动定位失败,继续尝试
-----------------
当前文档顶部偏移量:${Math.trunc($(document).scrollTop())}
期望文档顶部偏移量:${player_offset_top - offset_top}
偏移量误差:${(player_offset_top - offset_top) - Math.trunc($(document).scrollTop())}
播放器顶部偏移量:${player_offset_top}
设置偏移量:${offset_top}`)
}, 200)
const checkAutoLocationStatus = setInterval(() => {
const document_scroll_top = Math.trunc($(document).scrollTop())
const expect_offset_top = player_offset_top - offset_top
const offset_deviation = Math.abs(expect_offset_top - document_scroll_top)
const success = true ? offset_deviation < 5 : false
if (success) {
clearInterval(checkAutoLocationStatus)
clearInterval(applyAutoLocationInterval)
// logger.info(offset_deviation);
resolve(true)
}
}, 100)
})
},
// 点击播放器自动定位至播放器
async clickPlayerAutoLocation () {
if (click_player_auto_locate) {
const $player = $('#bilibili-player')
const player_offset_top = Math.trunc($player.offset().top)
$('#bilibili-player').on('click', handleClick)
function handleClick (event) {
event.stopPropagation()
// logger.info(`1:${player_offset_top}, 2:${offset_top}, 3:${player_offset_top - offset_top}`)
scrollToPlayer(player_offset_top - offset_top)
}
}
},
// 点击时间锚点自动返回播放器
async jumpVideoTime () {
const clickTarget = player_type === 'video' ? '#comment' : '#comment_module'
const $clickTarget = $(clickTarget)
scrollToPlayer(player_offset_top - offset_top)
$clickTarget.unbind('click').on('click', '.video-time,.video-seek', function (event) {
event.stopPropagation()
$('html,body').scrollTop(scrollTop)
const targetTime = $(this).attr(player_type === 'video' ? 'data-video-time' : 'data-time')
const video = $('bwp-video')
video.currentTime = targetTime
video.play()
})
},
// 自动取消静音
autoCancelMute () {
autoCancelMuteTimes++
const cancelMuteButtn = $('.bpx-player-ctrl-muted-icon')
const cancelMuteButtnDisplay = cancelMuteButtn.css('display')
const cancelMuteButtnClass = cancelMuteButtn.attr('class')
if (autoCancelMuteTimes === 1) {
if (cancelMuteButtnDisplay === 'block') {
cancelMuteButtn.click()
logger.info('已自动取消静音')
}
}
},
// 自动选择最高画质
autoSelectVideoHighestQuality () {
autoSelectVideoHighestQualityTimes++
if (!auto_select_video_highest_quality) return
if (autoSelectVideoHighestQualityTimes === 1) {
let message
const no4K8K = $('.bpx-player-ctrl-quality ul > li').filter(function () {
const qualityText = $(this).children('span.bpx-player-ctrl-quality-text').text()
return (!qualityText.includes('4K') && !qualityText.includes('8K'))
}).eq(0)
const yes8K = $('.bpx-player-ctrl-quality ul > li').filter(function () {
return $(this).children('span.bpx-player-ctrl-quality-text').text().includes('8K')
}).eq(0)
const yes4K = $('.bpx-player-ctrl-quality ul > li').filter(function () {
return $(this).children('span.bpx-player-ctrl-quality-text').text().includes('4K')
}).eq(0)
const notVip = $('.bpx-player-ctrl-quality ul > li').eq($('.bpx-player-ctrl-quality ul > li').children('.bpx-player-ctrl-quality-badge-bigvip').length)
function autoSelectTargetQuality (target) {
target.length ? target.click() : logger.error('最高画质丨切换失败丨未找到清晰度切换元素')
}
if (is_vip) {
if (!contain_quality_4k && !contain_quality_8k) {
autoSelectTargetQuality(no4K8K)
message = '最高画质|VIP|不包含4K及8K|切换成功'
}
if (contain_quality_4k && !contain_quality_8k) {
yes4K.length ? autoSelectTargetQuality(yes4K) : autoSelectTargetQuality(no4K8K)
message = '最高画质|VIP|4K|切换成功'
}
if ((contain_quality_4k && contain_quality_8k) || (!contain_quality_4k && contain_quality_8k)) {
yes8K.length ? autoSelectTargetQuality(yes8K) : autoSelectTargetQuality(no4K8K)
message = '最高画质|VIP|8K|切换成功'
}
} else {
autoSelectTargetQuality(notVip)
message = '最高画质|非VIP|切换成功'
}
logger.info(message)
}
},
// 添加样式文件
addPluginStyle () {
const style = `
#playerAdjustment {
height: 500px;
overflow: auto;
overscroll-behavior: contain;
padding-right: 10px;
}
.swal2-popup {
width: 34em !important;
padding: 1.25em !important;
}
.swal2-html-container {
margin: 0 !important;
padding: 16px 5px 0 !important;
width: 100% !important;
box-sizing: border-box !important;
}
.swal2-footer {
flex-direction: column !important;
}
.swal2-close {
top: 5px !important;
right: 3px !important;
}
.swal2-actions {
margin: 7px auto 0 !important;
}
.swal2-styled.swal2-confirm {
background-color: #23ade5 !important;
}
.swal2-icon.swal2-info.swal2-icon-show {
display: none !important;
}
.player-adjustment-container, .swal2-container {
z-index: 999999999 !important;
}
.player-adjustment-popup {
font-size: 14px !important;
}
.player-adjustment-setting-label {
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
padding-top: 10px !important;
}
.player-adjustment-setting-checkbox {
width: 16px !important;
height: 16px !important;
}
.player-adjustment-setting-tips {
width: 100% !important;
display: flex !important;
align-items: center !important;
padding: 5px !important;
margin-top: 10px !important;
background: #f5f5f5 !important;
box-sizing: border-box !important;
font-size: 14px !important;
color: #666 !important;
border-radius: 2px !important;
text-align: left !important;
}
.player-adjustment-setting-tips svg {
margin-right: 5px !important;
}
label.player-adjustment-setting-label input {
border: 1px solid #cecece !important;
background: #fff !important;
}
label.player-adjustment-setting-label input[type=checkbox],
label.player-adjustment-setting-label input[type=radio] {
width: 16px !important;
height: 16px !important;
}
label.player-adjustment-setting-label input:checked {
border-color: #1986b3 !important;
background: #23ade5 !important;
}
.auto-quality-sub-options,
.auto-locate-sub-options {
display: flex;
align-items: center;
padding-left: 15px;
}
.auto-quality-sub-options label.player-adjustment-setting-label.fourK,
.auto-locate-sub-options label.player-adjustment-setting-label.video {
margin-right: 10px;
}
.auto-quality-sub-options .player-adjustment-setting-label input[type="checkbox"] {
margin-left: 5px !important;
}
.player-adjustment-setting-label.screen-mod input {
margin-right: 5px !important;
}
#biliMainHeader {
height:64px!important;
}
#viewbox_report {
height:106px!important;
padding-top:24px!important;
}
#v_upinfo {
height:80px!important;
}
.members-info-v1 {
padding-top:0!important;
}
.members-info-v1 .wide-members-header {
height:0!important;
}
.members-info-v1 .wide-members-container .up-card .info-tag {
display:none!important;
}
.membersinfo-wide {
margin-top: -35px!important;
}
.fixed-sidenav-storage-item.locate {
width:40px!important;
height:40px!important;
}
.fixed-header .bili-header__bar{
position: absolute!important;
inset-inline: 0!important;
}
`
const addStyleToHead = () => {
addStyle('swal-pub-style', 'style', GM_getResourceText('swalStyle'))
addStyle('player-adjustment-style', 'style', style)
}
if (document.head) {
addStyleToHead()
} else {
const headObserver = new MutationObserver(() => {
if (document.head) {
headObserver.disconnect()
addStyleToHead()
}
})
headObserver.observe(document.documentElement, {
childList: true,
subtree: true,
})
}
},
// 注册脚本设置控件
registerMenuCommand () {
GM_registerMenuCommand('设置', () => {
const html = `
<div id="playerAdjustment" style="font-size: 1em;">
<label class="player-adjustment-setting-label" style="padding-top:0!important;"> 是否为大会员
<input type="checkbox" id="Is-Vip" ${getValue('is_vip') ? 'checked' : ''
} class="player-adjustment-setting-checkbox">
</label>
<span class="player-adjustment-setting-tips"> -> 请如实勾选,否则影响自动选择清晰度</span>
<label class="player-adjustment-setting-label"> 自动定位至播放器
<input type="checkbox" id="Auto-Locate" ${getValue('auto_locate') ? 'checked' : ''
} class="player-adjustment-setting-checkbox">
</label>
<div class="auto-locate-sub-options">
<label class="player-adjustment-setting-label video"> 普通视频(video)
<input type="checkbox" id="Auto-Locate-Video" ${getValue('auto_locate_video') ? 'checked' : ''
} class="player-adjustment-setting-checkbox">
</label>
<label class="player-adjustment-setting-label bangumi"> 其他视频(bangumi)
<input type="checkbox" id="Auto-Locate-Bangumi" ${getValue('auto_locate_bangumi') ? 'checked' : ''
} class="player-adjustment-setting-checkbox">
</label>
</div>
<span class="player-adjustment-setting-tips"> -> 只有勾选自动定位至播放器,才会执行自动定位的功能;勾选自动定位至播放器后,video 和 bangumi 两者全选或全不选,默认在这两种类型视频播放页都执行;否则勾选哪种类型,就只在这种类型的播放页才执行。</span>
<label class="player-adjustment-setting-label" id="player-adjustment-Range-Wrapper">
<span>播放器顶部偏移(px)</span>
<input id="Top-Offset" value="${getValue(
'offset_top'
)}" style="padding:5px;width: 200px;border: 1px solid #cecece;">
</label>
<span class="player-adjustment-setting-tips"> -> 播放器距离浏览器窗口默认距离为 ${Math.trunc(
$('#bilibili-player').offset().top
)};请填写小于 ${Math.trunc(
$('#bilibili-player').offset().top
)} 的正整数或 0;当值为 0 时,播放器上沿将紧贴浏览器窗口上沿、值为 ${Math.trunc(
$('#bilibili-player').offset().top
)} 时,将保持B站默认。 </span>
<label class="player-adjustment-setting-label"> 点击播放器时定位
<input type="checkbox" id="Click-Player-Auto-Location" ${getValue('click_player_auto_locate') ? 'checked' : ''
} class="player-adjustment-setting-checkbox">
</label>
<div class="player-adjustment-setting-label screen-mod" style="display: flex;align-items: center;justify-content: space-between;"> 播放器默认模式 <div style="width: 215px;display: flex;align-items: center;justify-content: space-between;">
<label class="player-adjustment-setting-label" style="padding-top:0!important;">
<input type="radio" name="Screen-Mod" value="close" ${getValue('selected_screen_mode') === 'close'
? 'checked'
: ''
}>关闭</label>
<label class="player-adjustment-setting-label" style="padding-top:0!important;">
<input type="radio" name="Screen-Mod" value="wide" ${getValue('selected_screen_mode') === 'wide'
? 'checked'
: ''
}>宽屏</label>
<label class="player-adjustment-setting-label" style="padding-top:0!important;">
<input type="radio" name="Screen-Mod" value="web" ${getValue('selected_screen_mode') === 'web'
? 'checked'
: ''
}>网页全屏</label>
</div>
</div>
<span class="player-adjustment-setting-tips"> -> 若遇到不能自动选择播放器模式可尝试点击重置</span>
<label class="player-adjustment-setting-label"> 网页全屏模式解锁
<input type="checkbox" id="Webfull-Unlock" ${getValue('webfull_unlock') ? 'checked' : ''
} class="player-adjustment-setting-checkbox">
</label>
<span class="player-adjustment-setting-tips"> ->*实验性功能(不稳,可能会有这样或那样的问题):勾选后网页全屏模式下可以滑动滚动条查看下方评论等内容,2秒延迟后解锁(番剧播放页不支持)<br>->新增迷你播放器显示,不过比较简陋,只支持暂停/播放操作,有条件的建议还是直接使用浏览器自带的小窗播放功能。</span>
<label class="player-adjustment-setting-label"> 自动选择最高画质
<input type="checkbox" id="Auto-Quality" ${getValue('auto_select_video_highest_quality')
? 'checked'
: ''
} class="player-adjustment-setting-checkbox">
</label>
<div class="auto-quality-sub-options">
<label class="player-adjustment-setting-label fourK"> 是否包含4K画质
<input type="checkbox" id="Quality-4K" ${getValue('contain_quality_4k') ? 'checked' : ''
} class="player-adjustment-setting-checkbox">
</label>
<label class="player-adjustment-setting-label eightK"> 是否包含8K画质
<input type="checkbox" id="Quality-8K" ${getValue('contain_quality_8k') ? 'checked' : ''
} class="player-adjustment-setting-checkbox">
</label>
</div>
<span class="player-adjustment-setting-tips"> -> 网络条件好时可以启用此项,勾哪项选哪项,都勾选8k,否则选择4k及8k外最高画质。</span>
<label class="player-adjustment-setting-label"> 自动刷新
<input type="checkbox" id="Auto-Reload" ${getValue('auto_reload') ? 'checked' : ''
} class="player-adjustment-setting-checkbox">
</label>
<span class="player-adjustment-setting-tips"> -> (不建议开启)若脚本执行失败是否自动刷新页面重试,开启后可能会对使用体验起到一定改善作用,但若是因为B站页面改版导致脚本失效,则会陷入页面无限刷新的情况,此时则必须在页面加载时看准时机关闭此项才能恢复正常,请自行选择是否开启。</span>
</div>
`
Swal.fire({
title: '播放页调整设置',
html: html,
icon: 'info',
showCloseButton: true,
showDenyButton: true,
confirmButtonText: '保存',
denyButtonText: '重置',
footer: '<div style="text-align: center;">如果发现脚本不能用,可能是播放页更新了,请耐心等待适配。</div><hr style="border: none;height: 1px;margin: 12px 0;background: #eaeaea;"><div style="text-align: center;font-size: 1.25em;"><a href="//userstyles.world/style/241/nightmode-for-bilibili-com" target="_blank">夜间哔哩 - </a><a href="//greasyfork.org/zh-CN/scripts/415804-bilibili%E6%92%AD%E6%94%BE%E9%A1%B5%E8%B0%83%E6%95%B4-%E8%87%AA%E7%94%A8" target="_blank">检查更新</a></div>',
}).then(res => {
res.isConfirmed && location.reload(true)
if (res.isConfirmed) {
location.reload(true)
} else if (res.isDenied) {
setValue('current_screen_mode', 'normal')
location.reload(true)
}
})
$('#Is-Vip').change(e => {
setValue('is_vip', e.target.checked)
$('.fourK,.eightK').css('display', e.target.checked ? 'flex!important' : 'none!important')
})
$('#Auto-Locate').change(e => {
setValue('auto_locate', e.target.checked)
})
$('#Auto-Locate-Video').change(e => {
setValue('auto_locate_video', e.target.checked)
})
$('#Auto-Locate-Bangumi').change(e => {
setValue('auto_locate_bangumi', e.target.checked)
})
$('#Top-Offset').change(e => {
setValue('offset_top', e.target.value * 1)
})
$('#Click-Player-Auto-Location').change(e => {
setValue('click_player_auto_locate', e.target.checked)
})
$('#Auto-Quality').change(e => {
setValue('auto_select_video_highest_quality', e.target.checked)
})
$('#Quality-4K').change(e => {
setValue('contain_quality_4k', e.target.checked)
})
$('#Quality-8K').change(e => {
setValue('contain_quality_8k', e.target.checked)
})
$('input[name="Screen-Mod"]').click(function () {
setValue('selected_screen_mode', $(this).val())
})
$('#Webfull-Unlock').change(e => {
setValue('webfull_unlock', e.target.checked)
})
$('#Auto-Reload').change(e => {
setValue('auto_reload', e.target.checked)
})
})
},
// 冻结视频标题及UP主信息样式
freezeHeaderAndVideoTitleStyles () {
$('#biliMainHeader').attr('style', 'height:64px!important')
$('#viewbox_report').attr('style', 'height:106px!important;padding-top:24px!important')
$('#v_upinfo').attr('style', 'height:80px!important')
$('.members-info-v1').attr('style', 'padding-top:0!important')
$('.members-info-v1 .wide-members-header').attr('style', 'height:0!important')
$('.members-info-v1 .wide-members-container .up-card .info-tag').attr('style', 'display:none!important')
},
// 判断当前窗口是否在最上方
isTopWindow () {
return window.self === window.top
},
// 前期准备函数
thePrepFunction () {
thePrepFunctionRunningTimes++
if (thePrepFunctionRunningTimes === 1) {
isLogin()
checkBrowserHistory()
historyListener()
this.initValue()
this.addPluginStyle()
this.isTopWindow() && this.registerMenuCommand()
this.getCurrentPlayerType()
this.getCurrentScreenMode()
}
},
// 主函数
async theMainFunction () {
try {
theMainFunctionRunningTimes++
if (theMainFunctionRunningTimes === 1) {
const videoPlayerExists = await checkElementExistence('#bilibili-player video', 5, 100) || await checkElementExistence('bwp-video', 5, 100)
if (videoPlayerExists) {
logger.info('播放器|存在')
$('body').css('overflow', 'hidden')
const isPlayable = await this.checkVideoCanPlayThrough()
// console.time('播放页调整:判断按钮出现')
const screenModeBtnExists = await checkElementExistence('#bilibili-player .bpx-player-ctrl-btn', 100, 100)
// console.timeEnd('播放页调整:判断按钮出现')
// const pageComplete = await checkPageReadyState('complete')
if (isPlayable || (!isPlayable && screenModeBtnExists)) {
logger.info('视频资源|可以播放')
// console.time('播放页调整:切换模式耗时')
this.watchScreenModeChange()
await sleep(100)
// close 为功能关闭,勿改
const selectedScreenMode = selected_screen_mode !== 'close' ? await this.autoSelectScreenMode() : 'close'
// console.timeEnd('播放页调整:切换模式耗时')
if (selectedScreenMode && selectedScreenMode.done || selectedScreenMode === 'close') {
if (selectedScreenMode !== 'close') logger.info(`屏幕模式|${selectedScreenMode['mode'].toUpperCase()}|切换成功`)
this.autoCancelMute()
// console.time('播放页调整:选择画质耗时')
this.autoSelectVideoHighestQuality()
// console.timeEnd('播放页调整:选择画质耗时')
this.clickPlayerAutoLocation()
if (webfull_unlock && selectedScreenMode.mode === 'web') {
this.fixedWebfullUnlockStyle()
}
// console.time('播放页调整:自动定位耗时')
this.freezeHeaderAndVideoTitleStyles()
await sleep(500)
const autoLocationDone = await this.autoLocation()
// console.timeEnd('播放页调整:自动定位耗时')
if (auto_locate && autoLocationDone) {
$('body').css('overflow', 'auto')
logger.info('自动定位|成功')
await sleep(100)
this.insertBackToPlayerButton()
this.jumpVideoTime()
}
if (!auto_locate || (auto_locate && auto_locate_bangumi && !auto_locate_video && player_type === 'video') || (auto_locate && auto_locate_video && !auto_locate_bangumi && player_type === 'bangumi')) {
$('body').css('overflow', 'auto')
logger.info('自动定位|未开启')
}
if (player_type === 'video') {
const loaded = await checkElementExistence('#comment > .comment > .bili-comment', 10, 100)
await sleep(100)
if (loaded) {
logger.info('页面加载|完毕')
} else {
pageReload()
}
}
} else {
logger.error(`屏幕模式|切换失败|autoSelectScreenMode()`)
pageReload()
}
} else {
logger.error('视频资源|加载失败')
pageReload()
}
} else {
logger.error('播放器|不存在')
pageReload()
}
}
} catch (error) {
logger.error(error)
pageReload()
}
},
}
if (isLogin()) {
m.thePrepFunction()
const checkDocumentHidden = setInterval(() => {
const dicumentHidden = isDocumentHidden()
if (!dicumentHidden) {
logger.info('当前标签|已激活|开始应用配置')
clearInterval(checkDocumentHidden)
m.theMainFunction()
} else {
logger.info('当前标签|未激活|等待激活')
}
}, 100)
} else logger.warn('请登录|本脚本只能在登录状态下使用')
})