// ==UserScript==
// @name 哔哩哔哩(bilibili | B站) 视频播放速度调整器
// @namespace http://tampermonkey.net/
// @version 0.16
// @description 增强视频播放观感, 提升学习效率
// @author WindOfCast
// @require https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.2.1/jquery.min.js
// @include *://www.bilibili.com/video/*
// @include *//www.bilibili.com/medialist/play/watchlater/*
// @include *//www.bilibili.com/list/watchlater*
// @include *//www.bilibili.com/list/*
// @icon data:image/png;base64,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
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_notification
// @homepage https://gitee.com/windofcast/
// @website https://space.bilibili.com/367207503
// @license AGPL License
// ==/UserScript==
/*
* @since 0.5 添加快捷键控制
* - 添加快捷键控制框架
* - 快捷键_removeCurrentVideo
* - 快捷键_showShortcutKeyMenu
* @since 0.6 添加添加快捷键校验
* - 添加快捷键校验
* - 设置快捷键成功与否都有提示通知
* @since 0.7 更新 B站 稍后再看 播放页
* - 添加新的 B站 url 请求地址 *\//www.bilibili.com/list/watchlater*
* - 替换之前速度菜单列表点击目标
* @since 0.8 watchlater 中添加播放历史记录 与 添加删除已播放快捷键 与 自动点赞
* - 添加播放历史记录
* - 修改快捷键配置格式
* - 添加删除已播放快捷键 (替换 删除当前视频)
* - 自动点赞
* @since 0.9 修复 自动点赞 bug 评论全点? 并降低重试频率
* @since 0.10 修复 自动点赞 bug 修改为至多两次
* @since 0.11 修复 自动点赞 bug 匹配 video like
* @since 0.12 修复 自动点赞 bug 修改延时点赞逻辑
* @since 0.13 修复 删除已播放快捷键 bug 多版本多播放页
* @since 0.14 修复 播放历史记录 中获取当前bvid
* @since 0.15 修复 播放历史记录 中加载videoInfo 中历史 preVideoInfo 非空判断
*
* */
(function () {
'use strict';
function commonFunc() {
this.video = () => {
return document.querySelector("video")
}
this.GMGetValue = (name, value = null) => {
let storageValue = value;
if (typeof GM_getValue === 'function') {
storageValue = GM_getValue(name, value)
} else {
let o = window.localStorage.getItem(name);
if (o != null) {
storageValue = o;
}
}
return storageValue;
}
this.GMSetValue = (name, value) => {
if (typeof GM_setValue === 'function') {
GM_setValue(name, value)
} else {
window.localStorage.setItem(name, value)
}
}
this.GMAddStyle = (css) => {
let ms = document.createElement("style");
ms.textContent = css;
let doc = document.head || document.documentElement;
doc.append(ms)
}
this.addCommonHtmlCss = () => {
let commonCss = `
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none !important;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
input[type='text'] {
outline: none;
border: 1px solid #a9a9a9;
}`;
this.GMAddStyle(commonCss);
}
this.appendHtml = (htmlText) => {
let html = $(htmlText);
$(document.body).append(html);
}
this.randomNumber = () => {
return Math.ceil(Math.random() * 100000000);
}
this.createElementDiv = () => {
return document.createElement('div');
}
this.getShortcutKey = (cmd) => {
let shortcutKeyReg = /^(?<group>[01]{3})(?<key>.+)$/g; // 100F
let {group, key} = shortcutKeyReg.exec(cmd).groups;
let mod = parseInt(group, 2);
return Modifier.toString(mod) + key;
}
this.notification = (messageObj) => {
GM_notification({
title: messageObj.title, text: messageObj.message, timeout: messageObj.timeout || 3000,
});
}
this.moduleInfo = (title, iptType, ...iptAttrs) => {
return {title: title, iptType: iptType, iptAttrs: iptAttrs};
}
this.moduleInfoAttr = (iptAttrs) => {
let result = "";
if (iptAttrs === undefined || iptAttrs.length === 0) {
return result;
}
iptAttrs.forEach((item) => {
if (item.indexOf(":") > -1) {
let kvs = item.split(":");
result += ` ${kvs[0]}="${kvs[1]}"`;
} else {
result += " " + item;
}
})
return result;
}
this.moduleAutoLike = (isReady = false) => {
if (!this.autoLikeFlag || isReady) {
console.log("autoLike")
this.autoLikeFlag = true;
setTimeout(() => {
let btnVideoLike = $("div[class*=video-like],span[class^=like]");
if (!btnVideoLike.hasClass("on")) {
btnVideoLike.click();
}
this.autoLikeFlag = false;
}, 5000);
}
}
this.loadVideoInfo = () => {
let currentVideoInfo;
let isVUE;
try {
isVUE = window.parent.unsafeWindow.__VUE__;
} catch (e) {
isVUE = false;
}
if (isVUE) {
currentVideoInfo = this.__loadVueVideoInfo();
} else {
currentVideoInfo = this.__loadDefaultVideoInfo();
}
// todo 更新数据
let preVideoInfo = currentConfig.historyVideoInfo;
if (preVideoInfo != null) {
Object.keys(currentVideoInfo).forEach((cviKey) => {
let currentVideoItem = currentVideoInfo[cviKey], preVideoItem = preVideoInfo[cviKey];
if (preVideoItem === undefined) {
return;
}
let newObj = {
index: currentVideoItem.index, bv_id: cviKey, title: currentVideoItem.title
}
delete preVideoItem.index;
delete preVideoItem.bv_id;
let pKeys = Object.keys(preVideoItem);
if (pKeys.length > 0) {
pKeys.forEach((pk) => {
if (currentVideoItem[pk] === undefined) {
newObj[pk] = preVideoItem[pk];
}
})
}
currentVideoInfo[cviKey] = newObj;
})
}
let videoInfo = $.extend(true, {}, currentVideoInfo);
currentConfig.historyVideoInfo = videoInfo;
return videoInfo;
}
this.__loadVueVideoInfo = () => {
let currentVideoInfo = {};
$(".action-list-item").each((i, e) => {
let info = e["__vue__"]["info"];
currentVideoInfo[info.bv_id] = this.__createVideoInfo(i, info.bv_id, info.title);
})
return currentVideoInfo;
}
this.__loadDefaultVideoInfo = () => {
let currentVideoInfo = {};
$(".player-auxiliary-playlist-item").each((i, e) => {
let bv_id = $(e).attr("data-bvid");
let title = $(e).find(".player-auxiliary-playlist-item-title").attr("title");
currentVideoInfo[bv_id] = this.__createVideoInfo(i, bv_id, title);
})
return currentVideoInfo;
}
this.__createVideoInfo = (index, bv_id, title) => {
return {index: index, bv_id: bv_id, title: title}
};
this.KeyMap = {
8: "BackSpace",
27: "Esc",
32: "Spacebar",
33: "Page Up",
34: "Page Down",
35: "End",
36: "Home",
37: "Left Arrow",
38: "Up Arrow",
39: "Right Arrow",
40: "Dw Arrow",
45: "Insert",
46: "Delete",
48: "0",
49: "1",
50: "2",
51: "3",
52: "4",
53: "5",
54: "6",
55: "7",
56: "8",
57: "9",
65: "A",
66: "B",
67: "C",
68: "D",
69: "E",
70: "F",
71: "G",
72: "H",
73: "I",
74: "J",
75: "K",
76: "L",
77: "M",
78: "N",
79: "O",
80: "P",
81: "Q",
82: "R",
83: "S",
84: "T",
85: "U",
86: "V",
87: "W",
88: "X",
89: "Y",
90: "Z",
112: "F1",
113: "F2",
114: "F3",
115: "F4",
116: "F5",
117: "F6",
118: "F7",
119: "F8",
120: "F9",
121: "F10",
122: "F11",
123: "F12",
186: ";",
187: "等号",
188: "逗号",
189: "减号",
190: ".>",
191: "/",
192: "`",
219: "[",
220: "\\",
221: "]",
222: "引号",
}
this.moduleList = {
// removeCurrentVideo: this.moduleInfo("移除当前视频/关闭当前视频单页快捷键:", "text", "readonly"),
showModuleMenu: this.moduleInfo("设置快捷键菜单快捷键:", "text", "readonly"),
removeHistoryVideo: this.moduleInfo("移除已播放视频快捷键:", "text", "readonly"),
autoLike: this.moduleInfo("自动点赞", "checkbox"),
};
this.commSettings = "";
this.autoLikeFlag = false;
}
const commonFuncObj = new commonFunc();
commonFuncObj.addCommonHtmlCss();
// alt ctrl shift
const defaultBiliBiliConfig = {
shortcutKeyMap: {
/**
* @deprecated
*/
removeCurrentVideo: "110F", showModuleMenu: "011M", removeHistoryVideo: "110H"
}, moduleMap: {
autoLike: false,
}
}
let currentConfig = commonFuncObj.GMGetValue("bilibili_current_config") || defaultBiliBiliConfig;
class Modifier {
static isAlt(mod) {
return (mod & Modifier.ALT) !== 0;
}
static isCtrl(mod) {
return (mod & Modifier.CTRL) !== 0;
}
static isShift(mod) {
return (mod & Modifier.SHIFT) !== 0;
}
static toString(mod) {
let sb = "";
let len;
if (Modifier.isAlt(mod)) sb += "Alt + ";
if (Modifier.isCtrl(mod)) sb += "Ctrl + ";
if (Modifier.isShift(mod)) sb += "Shift + ";
// if ((len = sb.length) > 0) {
// return sb.substring(0, len - 1);
// }
// return "";
return sb;
}
static get ALT() {
return 0x4;
}
static get CTRL() {
return 0x2;
}
static get SHIFT() {
return 0x1;
}
}
/**
*
* @href: https://www.cnblogs.com/jianglijs/p/14682059.html
* @author: 天长地久-无为
*/
class Enumerable {
static closeEnum() {
const enumKeys = []
const enumValues = []
// Traverse the enum entries
for (const [key, value] of Object.entries(this)) {
enumKeys.push(key)
value.enumKey = key
value.enumOrdinal = enumValues.length
enumValues.push(value)
}
// Important: only add more static properties *after* processing the enum entries
this.enumKeys = enumKeys
this.enumValues = enumValues
// TODO: prevent instantiation now. Freeze `this`?
}
/** Use case: parsing enum values */
static enumValueOf(str) {
const index = this.enumKeys.indexOf(str)
if (index >= 0) {
return this.enumValues[index]
}
return undefined
}
static [Symbol.iterator]() {
return this.enumValues[Symbol.iterator]()
}
toString() {
return this.constructor.name + '.' + this.enumKey
}
}
const popup = (function () {
class Popup {
constructor() {
this.mask = commonFuncObj.createElementDiv();
this.content = commonFuncObj.createElementDiv();
this.setStyle(this.mask, {
'width': '100%',
'height': '100%',
'background-color': 'rgba(0, 0, 0, 0.65)',
'position': 'fixed',
'left': '0px',
'top': '0px',
'bottom': '0px',
'right': '0px',
'z-index': '99999999',
});
this.setStyle(this.content, {
'max-width': '550px',
'width': '100%',
'max-height': '550px',
'background-color': 'rgb(255, 255, 255)',
'box-shadow': 'rgb(153, 153, 153) 0px 0px 2px',
'position': 'absolute',
'left': '50%',
'top': '50%',
'transform': 'translate(-50%, -50%)',
'border-radius': '3px',
});
this.mask.appendChild(this.content);
}
middleBox(param) {
this.content.innerText = ''; // 预清除原内容
let titleStr = '标题';
let obj = {};
// 参数类型检验
if (obj.toString.call(param) === '[object String]') {
titleStr = param;
} else if (obj.toString.call(param) === '[object Object]') {
titleStr = param.title;
}
// 显示遮罩
document.body.appendChild(this.mask);
// 创建 title 组件
this.title = commonFuncObj.createElementDiv();
this.setStyle(this.title, {
"width": '100%',
"height": '40px',
"line-height": '40px',
"box-sizing": 'border-box',
"background-color": "rgb(255, 77, 64)",
"color": 'rgb(255, 255, 255)',
"text-align": 'center',
"font-weight": "700",
"font-size": "16px",
});
this.title.innerText = titleStr;
this.content.appendChild(this.title);
// 关闭 button
this.closeBtn = commonFuncObj.createElementDiv();
this.closeBtn.innerText = '×';
this.setStyle(this.closeBtn, {
"text-decoration": 'none',
"color": 'rgb(255, 255, 255)',
"position": 'absolute',
"right": '10px',
"top": '0px',
"font-size": '25px',
"display": "inline-block",
"cursor": "pointer",
});
this.title.appendChild(this.closeBtn);
$(this.closeBtn).on('click', () => this.close())
}
/**
* 弹出提示框
* @param 参数
*/
dialog(param) {
this.middleBox(param);
this.dialogContent = commonFuncObj.createElementDiv();
this.setStyle(this.dialogContent, {
'max-height': '550px', 'padding': '12px',
})
this.dialogContent.innerHTML = param.content;
this.content.appendChild(this.dialogContent);
param.onReady(this);
}
close() {
commonFuncObj.commSettings = "";
commonFuncObj.GMSetValue("bilibili_current_config", currentConfig);
document.body.removeChild(this.mask);
}
setStyle(element, styleObj) {
Object.keys(styleObj).forEach((item) => {
element.style[item] = styleObj[item];
})
}
}
let popup = null;
return (function () {
if (!popup) {
popup = new Popup();
}
return popup;
})();
})();
function bilibiliHelper() {
this.controlSpeed = function () {
function innerClassControlSpeed() {
this.elementId = commonFuncObj.randomNumber();
this.ciId = "bilibili_ci_" + this.elementId;
this.ciSearchId = "#" + this.ciId;
this.speedMin = 0.5;
this.speedMax = 4;
this.speedStep = 0.05;
this.speedReg = /^([1-4]|0\.[5-9][0-9]*|[1-3]\.[0-9]{0,2}|4\.0+)$/;
this.video = commonFuncObj.video();
this.createElementHtml = () => {
let value = this.__getCiValue();
let cssText = `
#` + this.ciId + ` {
position: fixed;
right: 12px;
bottom: 349px;
z-index: 100;
max-width: 25px;
}`;
let htmlText = `
<div id="bilibili_cs_` + this.elementId + `">
<label for="` + this.ciId + `"></label><input id="` + this.ciId + `" type="text" value="` + value + `" min="0.5" max="4" step="0.05">
</div>`;
commonFuncObj.GMAddStyle(cssText);
commonFuncObj.appendHtml(htmlText);
this.__controlSpeed(value);
}
this.createEventListener = () => {
try {
const $ciSearch = $(this.ciSearchId);
$ciSearch.click();
$ciSearch.blur();
$ciSearch.on("mouseover", this.__eventMouseover);
$ciSearch.on("mouseout", this.__eventMouseout);
$ciSearch.on("wheel", this.__eventWheel);
$ciSearch.on("change", this.__eventChange);
$("video").on("loadedmetadata", this.__eventLoadedMetadata)
this.__rebindEventMenuListClick();
} catch (e) {
console.error("InnerClassControlSpeed.createEventListener " + e)
}
}
this.__eventMouseover = (e) => {
e.target.focus();
}
this.__eventMouseout = (e) => {
e.target.blur();
}
this.__eventWheel = (e) => {
const $ci = e.target, direction = e.originalEvent.deltaY;
let value = parseFloat($ci.value);
if (direction > 0) {
value -= this.speedStep;
if (value < this.speedMin) {
value = this.speedMin;
}
} else if (direction < 0) {
value += this.speedStep;
if (value > this.speedMax) {
value = this.speedMax;
}
}
value = value.toFixed(2);
$ci.value = value;
this.__setCiValue(value);
this.__controlSpeed(value);
e.preventDefault()
}
this.__eventChange = (e) => {
const $ci = e.target;
const defaultValue = $ci.defaultValue, currentValue = $ci.value;
if (!this.speedReg.test(currentValue)) {
$ci.value = defaultValue;
return;
}
let value = parseFloat(currentValue);
if (value > this.speedMax) {
value = this.speedMax;
}
if (value < this.speedMin) {
value = this.speedMin;
}
$ci.value = value;
this.__setCiValue(value);
this.__controlSpeed(value);
}
this.__eventLoadedMetadata = (e) => {
this.__controlSpeed(this.__getCiValue());
this.__rebindEventMenuListClick()
}
this.__eventMenuClick = (e) => {
const $ci = $(this.ciSearchId)[0], $target = e.target;
let value = $($target).attr("data-value");
$ci.value = value;
this.__setCiValue(value);
this.__controlSpeed(value);
}
this.__controlSpeed = (speed) => {
this.video.playbackRate = speed;
}
this.__setCiValue = (value) => {
commonFuncObj.GMSetValue("bilibili_ci_value", value);
}
this.__getCiValue = () => {
let value = commonFuncObj.GMGetValue("bilibili_ci_value");
value = !!value ? value : 1;
return value;
}
this.__rebindEventMenuListClick = () => {
let pathname = window.location.pathname;
let speedMenuListClass = ".bpx-player-ctrl-playbackrate-menu-item";
// if (pathname.indexOf("watchlater") > 0) {
// speedMenuListClass = ".bpx-player-ctrl-playbackrate-menu-item"; // @since 0.7 bilibili-player-video-btn-speed-menu-list -> bpx-player-ctrl-playbackrate-menu-item
// } else if (pathname.indexOf("video") > 0) {
// speedMenuListClass = ".bpx-player-ctrl-playbackrate-menu-item";
// }
$(speedMenuListClass).on("click", this.__eventMenuClick);
}
this.start = function () {
this.createElementHtml();
this.createEventListener();
}
}
try {
(new innerClassControlSpeed()).start();
} catch (e) {
console.error("innerClassControlSpeed " + e)
}
};
this.controlModule = function () {
function innerClassControlModule() {
this.focus = false;
this.createEventListener = () => {
$(document).on("focus", "input:not([readonly]), textarea", () => {
this.focus = true;
});
$(document).on("blur", "input, textarea", () => {
this.focus = false;
});
$(document).on('keydown', this.__eventKeydown)
};
this.__eventKeydown = (e) => {
if (!e.altKey && !e.shiftKey && !e.ctrlKey && this.focus) {
return;
}
const k = (key) => (key ? 1 : 0);
let pressKey = commonFuncObj.KeyMap[e.keyCode] || null;
if (pressKey === null) {
return;
}
let command = `${k(e.altKey)}${k(e.ctrlKey)}${k(e.shiftKey)}${pressKey}`;
let keyMap = currentConfig.shortcutKeyMap;
if (commonFuncObj.commSettings) {
let id = commonFuncObj.commSettings;
// 新旧键位对比
if (command === currentConfig.shortcutKeyMap[id]) {
return;
}
let conflict = null;
// 新键位与其他键位是否冲突
Object.keys(keyMap).forEach((oid) => {
if (oid === id) {
return;
}
if (command === currentConfig.shortcutKeyMap[oid]) {
conflict = oid;
}
})
let newShortcutKeyStr = commonFuncObj.getShortcutKey(command);
let messageObj;
if (conflict) {
messageObj = {
title: "设置快捷键失败",
message: `${newShortcutKeyStr} 已经分配给:\n ${commonFuncObj.moduleList[conflict].title}`,
};
} else {
document.querySelector(`#${id}`).value = newShortcutKeyStr;
currentConfig.shortcutKeyMap[id] = command;
messageObj = {
title: "设置快捷键成功",
message: `${commonFuncObj.moduleList[id].title} 设置为\n ${newShortcutKeyStr}`,
};
}
commonFuncObj.notification(messageObj);
return;
}
switch (command) {
case keyMap.removeCurrentVideo:
return this.__skRemoveCurrentVideo();
case keyMap.showModuleMenu:
return this.showModuleMenu();
case keyMap.removeHistoryVideo:
return this.__skRemoveHistoryVideo();
}
}
this.showModuleMenu = () => {
let ML = commonFuncObj.moduleList;
let skItem = "";
Object.keys(ML).forEach((key) => {
let mItemObj = ML[key];
let attrs = commonFuncObj.moduleInfoAttr(mItemObj.iptAttrs);
skItem += `
<div>${mItemObj.title} <label for="${key}"></label> <input id="${key}" type="${mItemObj.iptType}"${attrs}></div>`;
});
let content = `
<div id="skm-box" style="font-size: 15px;">
${skItem}
</div>
`;
popup.dialog({
"title": "设置", "content": content, "onReady": function ($self) {
$("#skm-box input[type='text']").on('click mouseover', (e) => {
let $text = e.target, id = $text.id;
Object.keys(ML).forEach((key) => {
document.querySelector(`#${key}`).style.border = '1px solid #a9a9a9';
})
$text.style.border = '1px solid #3597ff';
commonFuncObj.commSettings = id;
}).each((index, ele) => {
let id = ele.id;
ele.value = commonFuncObj.getShortcutKey(currentConfig.shortcutKeyMap[id]);
})
$("#skm-box input[type='checkbox']").on("click", (e) => {
let $checkbox = e.target, id = $checkbox.id;
if (!currentConfig.moduleMap) {
currentConfig.moduleMap = {}
}
currentConfig.moduleMap[id] = $checkbox.checked;
}).each((index, ele) => {
let id = ele.id;
if (currentConfig.moduleMap[id]) {
$(ele).attr("checked", true);
}
})
}
})
}
/**
* @deprecated
*/
this.__skRemoveCurrentVideo = () => {
let pathname = window.location.pathname;
if (pathname.indexOf("watchlater") > 0) {
$('.player-auxiliary-playlist-item-active .player-auxiliary-playlist-item-img-del,.player-auxiliary-playlist-item-showp .player-auxiliary-playlist-item-img-del,.siglep-active .del-btn').click();
} else if (pathname.indexOf("video") > 0) {
window.close();
}
}
this.__skRemoveHistoryVideo = () => {
let allBtn = $(".player-auxiliary-playlist-list .player-auxiliary-playlist-item-img-del,.action-list-inner .del-btn");
commonFuncObj.loadVideoInfo();
let finishVideos = Object.values(currentConfig.historyVideoInfo)
.filter(video => video.finish);
if (finishVideos.length > 0) {
finishVideos.sort((v1, v2) => v2.index - v1.index);
let messageObj = {
title: "删除已播放成功",
}
let message = "";
for (let i = 0; i < finishVideos.length; i++) {
let fv = finishVideos[i];
message += `${fv.bv_id}: ${fv.title}\n`;
allBtn.get(fv.index).click();
}
messageObj.message = message;
messageObj.timeout = 5000;
commonFuncObj.notification(messageObj);
}
}
this.clearConfig = () => {
commonFuncObj.GMSetValue("bilibili_current_config", defaultBiliBiliConfig);
window.location.reload();
}
this.start = function () {
this.createEventListener();
GM_registerMenuCommand("设置", () => this.showModuleMenu())
GM_registerMenuCommand("还原设置", () => this.clearConfig())
}
}
try {
(new innerClassControlModule()).start();
} catch (e) {
console.error("innerClassControlModule " + e)
}
}
this.controlHistory = function () {
function innerClassControlHistory() {
this.video = commonFuncObj.video();
this.videoInfo = null;
this.currentBvid = null;
this.createEventListener = () => {
class VideoEvent extends Enumerable {
static abort = new VideoEvent();
static loadedmetadata = new VideoEvent();
static timeupdate = new VideoEvent();
static ended = new VideoEvent();
static _ = this.closeEnum();
constructor() {
super();
}
}
// abort loadedmetadata timeupdate ended
$(this.video).on(VideoEvent.enumKeys.join(" "), (e) => {
let event = VideoEvent.enumValueOf(e.type);
switch (event) {
case VideoEvent.loadedmetadata: {
this._setCurrentBvid(this._getCurrentBvid());
this._setCurrentVideoDuration();
if (currentConfig.moduleMap.autoLike) {
commonFuncObj.moduleAutoLike();
}
break;
}
case VideoEvent.abort : {
currentConfig.historyVideoInfo = this.videoInfo;
if (this._isFinishCurrentVideo()) {
this._setCurrentVideoCurrentTime();
}
this._setCurrentBvid(null);
commonFuncObj.GMSetValue("bilibili_current_config", currentConfig);
break
}
case VideoEvent.timeupdate: {
this._setCurrentVideoCurrentTime();
break;
}
case VideoEvent.ended: {
this._setCurrentVideoFinish();
break
}
}
})
}
this._setCurrentVideoDuration = () => {
if (this.currentBvid !== null) this.videoInfo[this.currentBvid].duration = this.video.duration;
}
this._setCurrentVideoCurrentTime = () => {
if (this.currentBvid !== null) {
let temp = this.videoInfo[this.currentBvid].currentTime;
let max = Math.max(this.video.currentTime, temp ? temp : 0);
this.videoInfo[this.currentBvid].currentTime = max;
}
}
this._setCurrentBvid = (bvid) => {
this.currentBvid = bvid;
}
this._getCurrentBvid = () => {
let bvid;
let urlParam = new URLSearchParams(window.location.search);
bvid = urlParam.get("bvid");
if (bvid !== null) {
return bvid;
}
let pathname = window.location.pathname;
bvid = pathname.substring(pathname.lastIndexOf("/") + 1)
return bvid
}
this._setCurrentVideoFinish = () => {
if (this.currentBvid !== null) {
this.videoInfo[this.currentBvid].finish = true;
}
}
this._isFinishCurrentVideo = () => {
if (this.currentBvid !== null) {
let duration = this.videoInfo[this.currentBvid].duration;
let currentTime = this.videoInfo[this.currentBvid].currentTime;
if (currentTime / duration * 100 > 90) {
return true;
}
}
return false;
}
this.start = () => {
// todo 1. 读取列表信息 并 合并历史数据
this.videoInfo = commonFuncObj.loadVideoInfo();
// todo 2. 绑定 video 监听
this.createEventListener();
}
}
try {
let pathname = window.location.pathname;
if (pathname.indexOf("watchlater") > 0) {
(new innerClassControlHistory()).start();
}
} catch (e) {
console.error("innerClassControlHistory " + e)
}
}
this.start = function () {
this.controlHistory();
this.controlSpeed();
this.controlModule();
}
}
try {
$(document).on("readystatechange", (e) => {
setTimeout(() => {
(new bilibiliHelper()).start();
console.log("[视频播放速度调整器]: 已加载!");
if (!currentConfig.moduleMap) {
currentConfig.moduleMap = {}
}
if (currentConfig.shortcutKeyMap.showShortcutKeyMenu) {
currentConfig.shortcutKeyMap = defaultBiliBiliConfig.shortcutKeyMap;
}
if (currentConfig.moduleMap.autoLike) {
commonFuncObj.moduleAutoLike(true);
}
}, 3000)
});
} catch (e) {
console.error(e);
}
})();