// ==UserScript==
// @name Bilibili剧场版播放
// @namespace https://greasyfork.org/zh-CN/users/1129769-pxoxq
// @version 0.2.1
// @description B站播放器剧场模式
// @author pxoxq
// @license AGPL-3.0-or-later
// @match https://www.bilibili.com/
// @match https://www.bilibili.com/?*
// @match https://www.bilibili.com/video/**
// @match https://www.bilibili.com/list/**
// @icon https://www.bilibili.com/favicon.ico
// @grant GM_addElement
// @grant GM_addStyle
// @grant window.onurlchange
// @require https://code.jquery.com/jquery-3.7.1.min.js
// ==/UserScript==
//========elmGetter===================
var elmGetter = function() {
const win = window.unsafeWindow || document.defaultView || window;
const doc = win.document;
const listeners = new WeakMap();
let mode = 'css';
let $;
const elProto = win.Element.prototype;
const matches = elProto.matches ||
elProto.matchesSelector ||
elProto.webkitMatchesSelector ||
elProto.mozMatchesSelector ||
elProto.oMatchesSelector;
const MutationObs = win.MutationObserver ||
win.WebkitMutationObserver ||
win.MozMutationObserver;
function addObserver(target, callback) {
const observer = new MutationObs(mutations => {
for (const mutation of mutations) {
if (mutation.type === 'attributes') {
callback(mutation.target);
if (observer.canceled) return;
}
for (const node of mutation.addedNodes) {
if (node instanceof Element) callback(node);
if (observer.canceled) return;
}
}
});
observer.canceled = false;
observer.observe(target, {childList: true, subtree: true, attributes: true});
return () => {
observer.canceled = true;
observer.disconnect();
};
}
function addFilter(target, filter) {
let listener = listeners.get(target);
if (!listener) {
listener = {
filters: new Set(),
remove: addObserver(target, el => listener.filters.forEach(f => f(el)))
};
listeners.set(target, listener);
}
listener.filters.add(filter);
}
function removeFilter(target, filter) {
const listener = listeners.get(target);
if (!listener) return;
listener.filters.delete(filter);
if (!listener.filters.size) {
listener.remove();
listeners.delete(target);
}
}
function query(all, selector, parent, includeParent, curMode) {
switch (curMode) {
case 'css':
const checkParent = includeParent && matches.call(parent, selector);
if (all) {
const queryAll = parent.querySelectorAll(selector);
return checkParent ? [parent, ...queryAll] : [...queryAll];
}
return checkParent ? parent : parent.querySelector(selector);
case 'jquery':
let jNodes = $(includeParent ? parent : []);
jNodes = jNodes.add([...parent.querySelectorAll('*')]).filter(selector);
if (all) return $.map(jNodes, el => $(el));
return jNodes.length ? $(jNodes.get(0)) : null;
case 'xpath':
const ownerDoc = parent.ownerDocument || parent;
selector += '/self::*';
if (all) {
const xPathResult = ownerDoc.evaluate(selector, parent, null, 7, null);
const result = [];
for (let i = 0; i < xPathResult.snapshotLength; i++) {
result.push(xPathResult.snapshotItem(i));
}
return result;
}
return ownerDoc.evaluate(selector, parent, null, 9, null).singleNodeValue;
}
}
function isJquery(jq) {
return jq && jq.fn && typeof jq.fn.jquery === 'string';
}
function getOne(selector, parent, timeout) {
const curMode = mode;
return new Promise(resolve => {
const node = query(false, selector, parent, false, curMode);
if (node) return resolve(node);
let timer;
const filter = el => {
const node = query(false, selector, el, true, curMode);
if (node) {
removeFilter(parent, filter);
timer && clearTimeout(timer);
resolve(node);
}
};
addFilter(parent, filter);
if (timeout > 0) {
timer = setTimeout(() => {
removeFilter(parent, filter);
resolve(null);
}, timeout);
}
});
}
return {
get currentSelector() {
return mode;
},
get(selector, ...args) {
let parent = typeof args[0] !== 'number' && args.shift() || doc;
if (mode === 'jquery' && parent instanceof $) parent = parent.get(0);
const timeout = args[0] || 0;
if (Array.isArray(selector)) {
return Promise.all(selector.map(s => getOne(s, parent, timeout)));
}
return getOne(selector, parent, timeout);
},
each(selector, ...args) {
let parent = typeof args[0] !== 'function' && args.shift() || doc;
if (mode === 'jquery' && parent instanceof $) parent = parent.get(0);
const callback = args[0];
const curMode = mode;
const refs = new WeakSet();
for (const node of query(true, selector, parent, false, curMode)) {
refs.add(curMode === 'jquery' ? node.get(0) : node);
if (callback(node, false) === false) return;
}
const filter = el => {
for (const node of query(true, selector, el, true, curMode)) {
const _el = curMode === 'jquery' ? node.get(0) : node;
if (refs.has(_el)) break;
refs.add(_el);
if (callback(node, true) === false) {
return removeFilter(parent, filter);
}
}
};
addFilter(parent, filter);
},
create(domString, ...args) {
const returnList = typeof args[0] === 'boolean' && args.shift();
const parent = args[0];
const template = doc.createElement('template');
template.innerHTML = domString;
const node = template.content.firstElementChild;
if (!node) return null;
parent ? parent.appendChild(node) : node.remove();
if (returnList) {
const list = {};
node.querySelectorAll('[id]').forEach(el => list[el.id] = el);
list[0] = node;
return list;
}
return node;
},
selector(desc) {
switch (true) {
case isJquery(desc):
$ = desc;
return mode = 'jquery';
case !desc || typeof desc.toLowerCase !== 'function':
return mode = 'css';
case desc.toLowerCase() === 'jquery':
for (const jq of [window.jQuery, window.$, win.jQuery, win.$]) {
if (isJquery(jq)) {
$ = jq;
break;
};
}
return mode = $ ? 'jquery' : 'css';
case desc.toLowerCase() === 'xpath':
return mode = 'xpath';
default:
return mode = 'css';
}
}
};
}();
//===========elmGetter================
const BWidePlayerGolbalConf = {
darkMode: false,
wideModeDefault: true, // 默认宽屏
shadowColers: {
dark: ['#d3d4d5', '#818283'],
white: ['#E3E5E7', '#F1F2F3']
},
indexBgDarkColor: '#22222B',
indexBgColor: 'white',
globalDarkClr: '#22222B',
wideModePlayerHeight: 700,
styles: {
globalPrettify: ``,
widePlayerStyles: ``,
}
}
function bWidePlayerFlushStyle() {
BWidePlayerGolbalConf.styles.widePlayerStyles = `
.bpx-player-ctrl-wide{
width:0px;
}
#wide-box::-webkit-scrollbar{
display:none;
}
#wide-box, html{
scrollbar-width:none;
}
#wide-box .mini-header__logo path{
fill: #fb7299;
}
#wide-box #bilibili-player{
width: 100vw !important;
}
#wide-box #playerWrap{
order: -1;
height: ${BWidePlayerGolbalConf.wideModePlayerHeight}px;
}
#wide-box #bilibili-player{
height:${BWidePlayerGolbalConf.wideModePlayerHeight}px;
position:relative;
}
/*右侧悬浮列表*/
#wide-box #bilibili-player #reco_list,
#wide-box #bilibili-player #multi_page,
#wide-box #bilibili-player .base-video-sections-v1,
#wide-box #bilibili-player .action-list-container{
position:absolute;
top:25px;
right:0px;
background-color: #f1f2f3d6;
overflow: hidden;
transition: all .28s linear;
border: 1px solid #1F1F1F;
width: 14px;
height: 400px;
opacity: 0.26;
z-index: 999;
}
#wide-box #bilibili-player .base-video-sections-v1:hover{
width: 280px;
height: auto;
opacity: 1;
}
#wide-box #bilibili-player .base-video-sections-v1:hover .video-sections-content-list{
max-height: unset;
height: ${BWidePlayerGolbalConf.wideModePlayerHeight - 200}px !important;
}
#wide-box #bilibili-player #reco_list{
border-radius: 8px;
top: 20px;
width: 10px;
overflow-y: scroll;
}
#wide-box #bilibili-player #reco_list::-webkit-scrollbar{
width: 0px;
}
#wide-box #bilibili-player #reco_list{
scrollbar-width:0px;
}
#wide-box #bilibili-player #reco_list .pic-box{
width: 90px;
height: 65px;
}
#wide-box #bilibili-player #reco_list .pic-box .video-awesome-img{
width:100%;
height:100%;
}
#wide-box #bilibili-player #reco_list .info .title{
-webkit-line-clamp: 1;
}
#wide-box #bilibili-player #reco_list:hover{
height: ${BWidePlayerGolbalConf.wideModePlayerHeight - 140}px;
width: 280px;
opacity:1;
padding: 8px;
}
#wide-box #bilibili-player .action-list-container .main .cover{
width: 70px;
}
#wide-box #bilibili-player .action-list-container:hover{
width: 270px;
height: auto;
opacity: 1;
}
#wide-box #bilibili-player .action-list-container:hover #playlist-video-action-list,
#wide-box #bilibili-player #playlist-video-action-list-body{
max-height: ${BWidePlayerGolbalConf.wideModePlayerHeight - 160}px;
}
#wide-box #bilibili-player #multi_page:hover{
width: auto;
height:auto;
opacity: 1;
}
#wide-box #bilibili-player #multi_page .cur-list{
max-height: ${BWidePlayerGolbalConf.wideModePlayerHeight - 160}px;
}
#wide-box #bilibili-player #multi_page .list-box li{
width:260px;
}
/*左侧栏视频信息左-包含播放器*/
#wide-box .left-container.scroll-sticky,
#wide-box div.playlist-container--left{
display: flex;
flex-direction: column;
}
/*收藏页去padding*/
#wide-box div.playlist-container{
padding: unset;
}
#wide-box div#mirror-vdcon{
justify-content: left;
}
/*右侧栏*/
#wide-box div.right-container.is-in-large-ab,
#wide-box div.playlist-container--right{
margin-top: ${BWidePlayerGolbalConf.wideModePlayerHeight}px;
}
#wide-box #danmukuBox{margin-top: 0;}
#wide-box div.video-container-v1{
padding: 0;
justify-content: left;
}
/*左栏除视频编辑器*/
#wide-box .left-container.scroll-sticky>div:not(#playerWrap),
#wide-box div.playlist-container--left>div:not(#playerWrap){
margin-left:100px;
}
#wide-box #biliMainHeader .bili-header__bar{
background-color: #000;
}
#wide-box #app .bpx-player-sending-bar{
background-color:black;
}
#wide-box #biliMainHeader .bili-header__bar a.default-entry{
color:white;
}
#wide-box #biliMainHeader .bili-header__bar li svg{color: white}
#wide-box .mini-header .right-entry .right-entry__outside .right-entry-text{color:white}
#wide-box .mini-header__title span{color:white !important;}
#wide-box .bpx-player-video-inputbar-wrap{background-color:#353232;}
`
}
class BilibiliPrettifyInject {
static flushStyle() {
bWidePlayerFlushStyle()
this.injectStyle(BWidePlayerGolbalConf.styles.indexSimplifyStyle)
this.injectStyle(BWidePlayerGolbalConf.styles.widePlayerStyles)
}
static injectStyle(styleStr) {
GM_addStyle(styleStr)
}
}
class BilibiliPlayMode {
// 记录Ctrl是否已经被点击一次
static ctrlReady = false
static init() {
bWidePlayerFlushStyle()
// 注入样式
BilibiliPrettifyInject.injectStyle(BWidePlayerGolbalConf.styles.widePlayerStyles)
// 按钮移位
// 事件绑定
this.__eventBind()
}
static __injectMenu() {
}
static __eventBind() {
// 全局按键事件绑定
document.onkeyup = (event) => {
// 1. 双击Ctrl
if (event.key == "Control") {
if (!this.ctrlReady) {
this.ctrlReady = true
setTimeout(() => {
this.ctrlReady = false
}, 300)
}
else {
this.ctrlReady = false
// do something
this.toggleWideMode()
}
}
};
}
static toggleWideMode() {
elmGetter.get('.pic-box picture', document, 2.5 * 60e3).then(res => {
console.log('xxxou: ', res)
const mainBox = 'body'
const boxId = $(mainBox).attr('id')
let newId = ''
if (!boxId) {
newId = 'wide-box'
this.videoFloatList()
}
else {
this.videoFloatList(false)
}
$(mainBox).attr('id', newId)
})
}
static videoFloatList(float = true) {
const wrapperSelector = '#bilibili-player'
if (float) {
const wrapper = $(wrapperSelector)
const favList = $('.action-list-container')
if (favList && favList.length) {
$(wrapper[0]).append(favList)
}
else {
const multiList = $('#multi_page')
// console.log('multi-page: ', multiList)
if (multiList && multiList.length) {
$(wrapper[0]).append(multiList)
}
else {
const hejiList = $('.base-video-sections-v1')
if (hejiList && hejiList.length) {
$(wrapper[0]).append(hejiList)
}
else {
const normalList = $('#reco_list')
// console.log('reco list: ', normalList )
if (normalList && normalList.length) {
$(wrapper[0]).append(normalList)
}
}
}
}
}
else {
const favList = $(`${wrapperSelector} .action-list-container`)
if (favList && favList.length) {
$('#danmukuBox').after($(favList))
}
else {
const multiList = $(`${wrapperSelector} #multi_page`)
if (multiList && multiList.length) {
$('#danmukuBox').after($(multiList))
}
else {
const hejiList = $(`${wrapperSelector} .base-video-sections-v1`)
if (hejiList && hejiList.length) {
$('#danmukuBox').after(hejiList)
}
else {
const normalList = $(`${wrapperSelector} #reco_list`)
if (normalList && normalList.length) {
$('#danmukuBox').after($(normalList))
}
}
}
}
}
}
}
let firstCtrl = true
function __bWidePlayerInit() {
bWidePlayerFlushStyle()
BilibiliPlayMode.init()
if (BWidePlayerGolbalConf.wideModeDefault) {
BilibiliPlayMode.toggleWideMode()
}
}
(function () {
'use strict';
__bWidePlayerInit()
})();