// ==UserScript==
// @name bilibili网页播放器增加倍速按钮
// @namespace voeoc
// @version 0.0.4
// @description 在B站(bilibili)网页播放器上增加一些额外的倍速按钮
// @author voeoc
// @include https://www.bilibili.com/video/*
// @icon https://www.google.com/s2/favicons?domain=bilibili.com
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-idle
// ==/UserScript==
(function() {
// 加入的按钮
const DEFAULT_SPEED_LIST_BUTTON = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3, 4, 5, 10, 100];
// 存储上一页面倍速的键
const GMKEY_LAST_SPEED_VALUE = "VOEOC_GMKEY_LAST_SPEED_VALUE";
// 存储倍速列表的键
const GMKEY_SPEED_LIST = "VOEOC_GMKEY_SPEED_LIST";
// 设置是否自动保存倍速
const GMKEY_AUTO_SAVE_SPEED = "VOEOC_GMKEY_AUTO_SAVE_SPEED";
function formatSpeedValue(speedValue) {
if (Number.isInteger(speedValue)) {
return `${speedValue.toString()}.0`;
} else {
return speedValue.toString();
}
}
function setSpeedButtonHighlight(btnSpeed) {
let nowHighlightButton = document.querySelector('.bilibili-player-active');
if (nowHighlightButton != null) {
nowHighlightButton.classList.remove("bilibili-player-active");
}
btnSpeed.classList.add("bilibili-player-active");
}
function setSpeed(speedValue) {
document.querySelector('video').playbackRate = speedValue;
// 修改显示的文字
let btnShowSpeed = document.querySelector('.bilibili-player-video-btn-speed-name');
if (speedValue != 1) {
btnShowSpeed.innerHTML = `${formatSpeedValue(speedValue)}x`;
} else {
btnShowSpeed.innerHTML = "倍速";
}
console.log(`voeoc: 成功设定倍速${speedValue}`)
}
function createThatButton(text) {
let btn = document.createElement("li");
btn.classList.add("bilibili-player-video-btn-speed-menu-list");
btn.classList.add("voeoc-virtual-button");
btn.innerHTML = text;
return btn;
}
function createSpeedButton(speedValue) {
let btnSpeed = createThatButton(`${formatSpeedValue(speedValue)}x`);
btnSpeed.setAttribute("data-value", `${speedValue}`);
btnSpeed.onclick = function() {
// 设定视频的倍速
setSpeed(speedValue);
console.log(`voeoc: 用户设置倍速${speedValue}`)
// 刷新高亮
setSpeedButtonHighlight(btnSpeed);
// 储存当前倍速
GM_setValue(GMKEY_LAST_SPEED_VALUE, speedValue);
// 隐藏弹出菜单
document.querySelector(".bilibili-player-video-btn.bilibili-player-video-btn-speed").classList.remove("bilibili-player-speed-show");
};
return btnSpeed;
}
function getIsAutoSaveSpeed() {
return GM_getValue(GMKEY_AUTO_SAVE_SPEED, false);
}
function appendSettingsUI() {
let isTheFirstRun = true;
waitElementLoaded(".bilibili-player-video-danmaku-setting", (bilibiliPlayerSettingsPanelButton) => {
bilibiliPlayerSettingsPanelButton.addEventListener("mouseover", function() {
if (!isTheFirstRun) {
return;
}
waitElementLoaded(".bilibili-player-video-danmaku-setting-right", (bilibiliPlayerSettingsPanel) => {
let btnSpeedSettings = document.createElement("div");
btnSpeedSettings.className = "voeoc-button-speed-settings";
btnSpeedSettings.innerHTML = `
修改倍速表...
<div id="voeoc-speed-list-settings-panel" class="voeoc-speed-list-settings-panel">
<div style="margin: 5px;">
<input id="voeoc-text-speed-list" class="voeoc-text-speed-list" style=""></input>
<label style="float:left; ">
<input type="checkbox" id="voeoc-cbox-auto-save-speed" style="vertical-align:middle;background-color: red;">
自动播放倍速
</label>
<div id="voeoc-button-speed-settings-cancel"class="bui bui-button bui-button-transparent voeoc-button-speed-settings-apply">取消</div>
<div id="voeoc-button-speed-settings-apply" class="bui bui-button bui-button-transparent voeoc-button-speed-settings-apply">确定</div>
</div>
</div>
`;
function showSettingPanel(isShow) {
let settingsPanel = bilibiliPlayerSettingsPanel.querySelector("#voeoc-speed-list-settings-panel");
if (isShow) {
let inputBoxSpeedList = bilibiliPlayerSettingsPanel.querySelector("#voeoc-text-speed-list");
let cboxAutoSaveSpeed = bilibiliPlayerSettingsPanel.querySelector("#voeoc-cbox-auto-save-speed");
// 载入倍速表,格式为数组
inputBoxSpeedList.value = GM_getValue(GMKEY_SPEED_LIST, DEFAULT_SPEED_LIST_BUTTON).toString();
// 载入是否自动保存的选项
cboxAutoSaveSpeed.checked = getIsAutoSaveSpeed();
settingsPanel.classList.add("voeoc-speed-list-settings-panel-show");
} else {
settingsPanel.classList.remove("voeoc-speed-list-settings-panel-show");
}
}
waitElementLoaded("#voeoc-button-speed-settings-apply", (btnOk) => {
btnOk.onclick = function() {
let inputBoxSpeedList = bilibiliPlayerSettingsPanel.querySelector("#voeoc-text-speed-list");
// 储存输入的倍速表
GM_setValue(GMKEY_SPEED_LIST, inputBoxSpeedList.value.split(" ").join("").replace(",", ",").split(","));
// 修改成功
// 隐藏设置面板
showSettingPanel(false);
// 刷新当前网页倍速表
reloadSpeedList();
};
});
waitElementLoaded("#voeoc-button-speed-settings-cancel", (btnCancel) => {
btnCancel.onclick = function() {
showSettingPanel(false);
};
});
waitElementLoaded("#voeoc-cbox-auto-save-speed", (cboxAutoSaveSpeed) => {
cboxAutoSaveSpeed.onclick = function() {
GM_setValue(GMKEY_AUTO_SAVE_SPEED, cboxAutoSaveSpeed.checked);
};
});
btnSpeedSettings.onmouseenter = function() {
showSettingPanel(true);
};
btnSpeedSettings.onmouseleave = function() {
let inputBoxSpeedList = bilibiliPlayerSettingsPanel.querySelector("#voeoc-text-speed-list");
if (inputBoxSpeedList != document.activeElement) {
// 如果倍速表输入框未获得焦点,取消显示设置面板
showSettingPanel(false);
}
};
bilibiliPlayerSettingsPanel.appendChild(btnSpeedSettings);
});
isTheFirstRun = false;
});
});
}
function reloadSpeedList() {
let btnMenu = document.querySelector(".bilibili-player-video-btn-speed-menu");
// 读取倍速表
let oldSpeedList = GM_getValue(GMKEY_SPEED_LIST, DEFAULT_SPEED_LIST_BUTTON);
// 读取上一页面的倍速设置
let lastSpeedValue = GM_getValue(GMKEY_LAST_SPEED_VALUE, 1);
// 清除冗余按钮
btnMenu.innerHTML = "";
// 添加按钮节点
for (let i = 0; i < oldSpeedList.length; ++i) {
let btnSpeed = createSpeedButton(Number(oldSpeedList[i]));
// 在最前面插入按钮
btnMenu.insertBefore(btnSpeed, btnMenu.childNodes[0]);
// 根据上一页面刷新倍速
if (getIsAutoSaveSpeed() && Math.abs(oldSpeedList[i] - lastSpeedValue) < 0.001) {
btnSpeed.click();
}
}
// 第一次加载页面,延时检测视频速度是否正确修改
if (getIsAutoSaveSpeed()) {
(function() {
const TIME_OUT = 10; // 10次放弃
let findTimeNum = 0; // 记录查找的次数
let timer = setInterval(() => {
if (lastSpeedValue != document.querySelector('video').playbackRate) {
// 清除定时器
clearInterval(timer);
setSpeed(lastSpeedValue);
} else {
findTimeNum++;
if (TIME_OUT < findTimeNum) {
// 清除定时器,并且不执行回调
clearInterval(timer);
}
}
}, 100);
})();
}
}
function waitElementLoaded(selector, func) {
const TIME_OUT = 100; // 找100次没有找到就放弃
let findTimeNum = 0; // 记录查找的次数
let timer = setInterval(() => {
let element = document.querySelector(selector);
console.log(`voeoc: 查找${selector}:${element}`)
if (element != null) {
// 清除定时器
clearInterval(timer);
func(element);
} else {
findTimeNum++;
if (TIME_OUT < findTimeNum) {
// 清除定时器,并且不执行回调
clearInterval(timer);
console.log(`voeoc: 查找${selector}失败`)
}
}
}, 200);
}
// 等待视频组件加载完毕
waitElementLoaded('video', (bilibiliPlayer) => {
// 注入自定义样式
(function() {
let style = document.createElement("style");
let cssText = `
.voeoc-virtual-button {}
.voeoc-button-speed-settings {
margin-top: 9px;
float: right;
width: 80px;
z-index: 999px;
transform: translateZ(0);
}
.voeoc-speed-list-settings-panel {
height: 90px;
width: 240px;
overflow: hidden;
top: -90px;
left: -150px;
float: right;
position: absolute;
visibility: hidden;
opacity: 0;
background-color: rgb(59, 59, 59);
margin: 0 -110px 0 0;
border-radius: 2px;
}
.voeoc-speed-list-settings-panel.voeoc-speed-list-settings-panel-show {
visibility: visible;
opacity: 1;
}
.voeoc-button-speed-settings-apply {
float: right;
position: relative;
top: 25px;
margin-right: 2px;
}
.voeoc-text-speed-list {
width: 225px;
background-color: transparent;
border-color: white;
border-width: 0;
border-bottom-width: 1px;
border-style: solid;
line-height: 22px;
}
`;
style.appendChild(document.createTextNode(cssText));
document.getElementsByTagName("head")[0].appendChild(style);
}());
let isFristLoad = true;
function loadData() {
// 等待视频速率控制组件加载完毕
waitElementLoaded(".bilibili-player-video-btn-speed-menu", (btnMenu) => {
// 载入倍速表
reloadSpeedList();
// 载入设置界面
appendSettingsUI();
});
};
loadData();
bilibiliPlayer.addEventListener("loadedmetadata", function() {
if (isFristLoad) {
isFristLoad = false;
return;
}
loadData();
});
});
})();