// ==UserScript==
// @name b站分P视频随机播放
// @namespace https://github.com/LU-JIEJIE/bilibili-random-play
// @version 1.1.0
// @author lu-jiejie
// @description 为B站分P视频添加随机播放功能
// @license MIT
// @icon https://www.bilibili.com/favicon.ico
// @homepage https://github.com/LU-JIEJIE/bilibili-random-play
// @match https://www.bilibili.com/video/*
// ==/UserScript==
(function () {
'use strict';
function setLocalStorage(key, value) {
window.localStorage.setItem(key, value);
}
function getLocalStorage(key) {
return window.localStorage.getItem(key);
}
function formatLog(logStr) {
console.log(`[Bilibili Random Play]: ${logStr}`);
}
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function createElementFromHTML(htmlString) {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, "text/html");
return doc.body.firstChild;
}
function insertBefore(newNode, referenceNode) {
var _a;
(_a = referenceNode.parentNode) == null ? void 0 : _a.insertBefore(newNode, referenceNode);
}
function insertAfter(newNode, referenceNode) {
var _a;
(_a = referenceNode.parentNode) == null ? void 0 : _a.insertBefore(newNode, referenceNode.nextSibling);
}
const NOT_LIST_TYPE_VIDEO = "当前页面不是分P视频,无法使用随机播放。";
const CONTROL_PREV_BUTTON = `<div class="bpx-player-ctrl-btn bpx-player-ctrl-prev" role="button" aria-label="上一个" tabindex="0" "=""><div class="bpx-player-ctrl-btn-icon"><span class="bpx-common-svg-icon"><svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" data-pointer="none" style="enable-background:new 0 0 22 22" viewBox="0 0 22 22"><path d="M16 5a1 1 0 0 0-1 1v4.615a1.431 1.431 0 0 0-.615-.829L7.21 5.23A1.439 1.439 0 0 0 5 6.445v9.11a1.44 1.44 0 0 0 2.21 1.215l7.175-4.555a1.436 1.436 0 0 0 .616-.828V16a1 1 0 0 0 2 0V6C17 5.448 16.552 5 16 5z"></path></svg></span></div></div>`;
const CONTROL_NEXT_BUTTON = `<div class="bpx-player-ctrl-btn bpx-player-ctrl-next" role="button" aria-label="下一个" tabindex="0"><div class="bpx-player-ctrl-btn-icon"><span class="bpx-common-svg-icon"><svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" data-pointer="none" style="enable-background:new 0 0 22 22" viewBox="0 0 22 22"><path d="M16 5a1 1 0 0 0-1 1v4.615a1.431 1.431 0 0 0-.615-.829L7.21 5.23A1.439 1.439 0 0 0 5 6.445v9.11a1.44 1.44 0 0 0 2.21 1.215l7.175-4.555a1.436 1.436 0 0 0 .616-.828V16a1 1 0 0 0 2 0V6C17 5.448 16.552 5 16 5z"></path></svg></span></div></div>`;
const SET_TIMEOUT_TIME = 500;
let isRandom = false;
let totalPage = 0;
let curRandomIndex = 0;
let randomList = [];
let videoList = [];
let prevButton;
let nextButton;
let lock = false;
function init() {
({ totalPage } = getPageInfo());
if (totalPage === 0) {
formatLog(NOT_LIST_TYPE_VIDEO);
return;
}
createControlButton();
resetControlButton();
initVideoList();
bindVideoListEvent();
}
function getPageInfo() {
const pageInfoSpan = document.querySelector("span.cur-page");
if (!pageInfoSpan)
return { curPage: 0, totalPage: 0 };
const [curPage, totalPage2] = pageInfoSpan.innerHTML.replace(/\(|\)/, "").split("/").map((item) => Number.parseInt(item));
return { curPage, totalPage: totalPage2 };
}
function initVideoList() {
setTimeout(() => {
videoList = Array.from(document.querySelectorAll("ul.list-box > li > a > div.clickitem"));
if (videoList.length === 0) {
initVideoList();
} else {
createRandomButton();
}
}, SET_TIMEOUT_TIME);
}
function createControlButton() {
prevButton = createElementFromHTML(CONTROL_PREV_BUTTON);
nextButton = createElementFromHTML(CONTROL_NEXT_BUTTON);
prevButton.addEventListener("click", () => {
if (isRandom)
playNext(-1);
else
videoList[videoList.length - 1].click();
});
nextButton.addEventListener("click", () => {
if (isRandom)
playNext();
else
videoList[0].click();
});
}
function prevButtonEvent(e) {
if (isRandom) {
playNext(-1);
e.stopPropagation();
e.preventDefault();
}
}
function nextButtonEvent(e) {
if (isRandom) {
playNext();
e.stopPropagation();
e.preventDefault();
}
}
function resetControlButton() {
changeControlButtonState("prev", "remove");
changeControlButtonState("next", "remove");
setTimeout(() => {
const nextButton2 = document.querySelector("div.bpx-player-ctrl-next");
const prevButton2 = document.querySelector("div.bpx-player-ctrl-prev");
if (nextButton2 && prevButton2) {
prevButton2.removeEventListener("click", prevButtonEvent);
nextButton2.removeEventListener("click", nextButtonEvent);
prevButton2.addEventListener("click", prevButtonEvent);
nextButton2.addEventListener("click", nextButtonEvent);
} else if (nextButton2) {
changeControlButtonState("prev", "insert");
} else if (prevButton2) {
changeControlButtonState("next", "insert");
} else {
resetControlButton();
}
}, SET_TIMEOUT_TIME);
}
function handleRandomState(state) {
if (state === "on") {
isRandom = true;
changeRandomButtonState(true);
setLocalStorage("isRandom", "true");
closeAutoPlay();
initRandomList();
bindVideoEvent();
} else if (state === "off") {
isRandom = false;
changeRandomButtonState(false);
setLocalStorage("isRandom", "");
}
}
function closeAutoPlay() {
lock = true;
const autoPlayButton = document.querySelector("#multi_page > div.head-con > div.head-right > span:nth-child(2) > span.switch-button");
if (autoPlayButton == null ? void 0 : autoPlayButton.classList.contains("on"))
autoPlayButton.click();
lock = false;
}
function playNext(distance = 1) {
curRandomIndex = (curRandomIndex + distance + totalPage) % randomList.length;
const nextPage = randomList[curRandomIndex];
videoList[nextPage - 1].click();
}
function bindVideoEvent() {
setTimeout(() => {
const video = document.querySelector("div.bpx-player-video-wrap > video");
if (video) {
video.addEventListener("ended", (event) => {
event.stopImmediatePropagation();
playNext();
});
} else {
bindVideoEvent();
}
}, SET_TIMEOUT_TIME);
}
function bindVideoListEvent() {
setTimeout(() => {
const videoList2 = document.querySelector("ul.list-box");
videoList2 == null ? void 0 : videoList2.addEventListener("click", (e) => {
if (e.target.tagName !== "UL") {
resetControlButton();
const curPage = +/p=(\d+)/.exec(document.location.href)[1];
curRandomIndex = randomList.findIndex((item) => item === curPage);
}
});
}, SET_TIMEOUT_TIME);
}
function changeControlButtonState(buttonType, buttonState) {
const playButton = document.querySelector("div.bpx-player-ctrl-play");
if (buttonType === "prev") {
if (buttonState === "insert")
insertBefore(prevButton, playButton);
else
prevButton.remove();
} else if (buttonType === "next") {
if (buttonState === "insert")
insertAfter(nextButton, playButton);
else
nextButton.remove();
}
}
function createRandomButton() {
const path = document.querySelector("#multi_page > div.head-con > div.head-right");
path.insertAdjacentHTML("afterbegin", `
<span class="next-button" id="random-button">
<span class="txt">随机</span>
<span class="switch-button"></span>
</span>
`);
document.querySelector("span#random-button").addEventListener("click", () => {
if (isRandom)
handleRandomState("off");
else
handleRandomState("on");
});
isRandom = Boolean(getLocalStorage("isRandom")) || false;
if (isRandom)
handleRandomState("on");
else
handleRandomState("off");
const autoPlayButton = document.querySelector("#multi_page > div.head-con > div.head-right > span:nth-child(2)");
autoPlayButton.addEventListener("click", () => {
if ((autoPlayButton == null ? void 0 : autoPlayButton.children[1].classList.contains("on")) && isRandom && !lock)
handleRandomState("off");
});
}
function changeRandomButtonState(on) {
const randomButton = document.querySelector("span#random-button .switch-button");
if (on)
randomButton.classList.add("on");
else
randomButton.classList.remove("on");
}
function initRandomList() {
randomList = Array.from({ length: totalPage }).fill(0).map((_, index) => index + 1);
for (let i = randomList.length - 1; i >= 0; i--) {
const randomIndex = randomInt(0, i);
[randomList[i], randomList[randomIndex]] = [randomList[randomIndex], randomList[i]];
}
const { curPage } = getPageInfo();
randomList.splice(randomList.indexOf(curPage), 1);
randomList.unshift(curPage);
curRandomIndex = 0;
}
init();
})();