Skip to content

Instantly share code, notes, and snippets.

@seognil
Last active November 15, 2022 09:43
Show Gist options
  • Save seognil/3e98d49515784df9b6cddf7d5d3a0ada to your computer and use it in GitHub Desktop.
Save seognil/3e98d49515784df9b6cddf7d5d3a0ada to your computer and use it in GitHub Desktop.
bilibili enhanced (personal usage)
/* override player top title bar mask */
.bpx-player-top-wrap .bpx-player-top-mask,
.bilibili-player-video-top .bilibili-player-video-top-mask {
background: none !important;
}
/* override player bottom control bar */
.bpx-player-control-wrap,
.bilibili-player-video-control-wrap {
opacity: 0.7;
}
#mod-messager {
position: absolute;
bottom: 150px;
left: 10px;
display: flex;
justify-content: center;
align-items: center;
background-color: hsla(0, 0%, 0%, 0.5);
color: white;
z-index: 1;
opacity: 0;
font-size: 20px;
transition-duration: 500ms;
}
#mod-messager.shown {
opacity: 1;
}
{
const getBiliVideo = () => document.querySelector("#bilibili-player, #bilibiliPlayer")?.querySelector(".bpx-player-video-wrap, .bilibili-player-video-wrap")?.querySelector("video");
// * ================================================================================ analyze
/**
* 1. single normal https://www.bilibili.com/video/BV1ct4y1N7BQ/
* 2. single movie https://www.bilibili.com/bangumi/play/ep399856
* 3. playlist anime https://www.bilibili.com/bangumi/play/ss41410
* 4. playlist anime https://www.bilibili.com/bangumi/play/ep691614
* 5. playlist custom https://www.bilibili.com/video/BV1W7411F79S/
* 6. playlist playall https://www.bilibili.com/medialist/play/7980111?from=space&business=space&sort_field=pubtime
* 7. mix activity page https://www.bilibili.com/blackboard/activity-yXPfn575pD.html?spm_id_from=333.1007.0.0
*
*
* [player]
* 1,5. bpx-player: normal, normal custom playlist
* 2,3,4. squirtle: bangumi anime/movie
* 6. bilibili-player: playall, mix activity
*
* .bilibili-player-video-wrap
*
*/
{
}
// * ================================================================================ auto
{
// * ---------------------------------------------------------------- s/player auto jump
if (document.URL.match("/s/video/")) {
location.replace(document.URL.replace("/s/video/", "/video/"));
}
// * ---------------------------------------------------------------- pause other tabs
{
const bc = new BroadcastChannel("bilibili-control");
const localId = Math.random().toString(16).slice(-6);
let localVideo;
bc.addEventListener("message", ({ data }) => {
document.querySelectorAll("video").forEach((e) => {
// * skip current video, only pause other videos
if (localId === data.triggerId && e === localVideo) return;
e.pause();
});
});
document.addEventListener(
"play",
(e) => {
// * skip misc videos (e.g. inline player)
if (e.target !== getBiliVideo()) return;
localVideo = e.target;
bc.postMessage({ triggerId: localId });
},
true
);
}
// * ---------------------------------------------------------------- playlist auto next
{
const checkAutoPlayForPlaylist = () => {
const playlist = document.querySelector(".ep-list-wrapper ul, .video-section-list, .player-auxiliary-playlist-list");
// * 自动切集
const autonext = document.querySelector(".bpx-player-ctrl-setting-handoff input[value='0'], .squirtle-handoff-auto, .bilibili-player-video-btn-setting-right-playtype input[value='1']");
// * 播完暂停
const stopnext = document.querySelector(".bpx-player-ctrl-setting-handoff input[value='2'], .squirtle-handoff-pause, .bilibili-player-video-btn-setting-right-playtype input[value='2']");
// @ts-ignore
playlist ? autonext?.click() : stopnext?.click();
};
document.addEventListener("play", checkAutoPlayForPlaylist, true);
}
}
// * ======================================================================================================================== Hotkeys
{
// * ================================================================================ Custom Hotkeys Handler
document.addEventListener("keydown", (e) => {
// * skip inputing
if (["INPUT", "TEXTAREA"].includes(document.activeElement?.tagName ?? "")) return;
if (false) "";
// * ---------------- original feature hotkey
else if (e.key === "[" || e.key === "PageUp") playlistCutOff(-1);
else if (e.key === "]" || e.key === "PageUp") playlistCutOff(1);
else if (e.key === "c") toggleDanmu();
else if (e.key === "t") toggleWebFull();
else if (e.key === "f" && !(e.metaKey || e.ctrlKey)) toggleScreenFull();
// * ---------------- time jump
// else if ("1234567890".split("").some((v) => v === e.key)) setPlaybackJumpToPercent(e.key);
else if (e.key === "Backspace") setPlaybackJumpToPercent(0);
else if (e.key === "q") setPlaybackJumpBySec(-jumpStep);
else if (e.key === "e") setPlaybackJumpBySec(+jumpStep);
else if (e.code === "Space") e.preventDefault(), togglePlay();
// * ---------------- speed
else if (e.key === "z") setPlaybackSpeedBy(-speedStep);
else if (e.key === "x") setPlaybackSpeedBy(+speedStep);
else if (e.key === "v") togglePlaybackSpeed();
// * ---------------- loop
else if (e.key === "r") setReplayLoop();
});
// * ================================================================================ Block Original Hotkeys
{
// * sentry walkaround
const legacyAddHandler = EventTarget.prototype.addEventListener;
Object.defineProperty(EventTarget.prototype, "addEventListener", {
get: function () {
return function (...args) {
const [eventname, fn, ...rest] = args;
if (eventname === "keydown") {
const hackFn = function (...args) {
("OVERRIDE_FLAG");
const [e] = args;
const MASK_LIST = "qwert asdfg zxcvb 01234567890";
if (MASK_LIST.includes(e.key)) return;
fn.call(this, ...args);
};
return legacyAddHandler.call(this, eventname, hackFn, ...rest);
} else {
return legacyAddHandler.call(this, ...args);
}
};
},
set: function () {},
});
const legacyRemoveHandler = EventTarget.prototype.removeEventListener;
Object.defineProperty(EventTarget.prototype, "removeEventListener", {
get: function () {
return function (...args) {
return legacyRemoveHandler.call(this, ...args);
};
},
set: function () {},
});
}
// * ================================================================================ Features
// * ---------------------------------------------------------------- playlist cut off
const playlistCutOff = (delta) => {
[
// 3,4,
{ container: ".ep-list-wrapper ul", cur: ".ep-item.cursor", target: ".ep-item" },
// 5,
{ container: ".video-section-list", cur: ".video-episode-card__info-playing", target: ".video-episode-card" },
// 6,
{ container: ".player-auxiliary-playlist-list", cur: ".player-auxiliary-playlist-item-active", target: ".player-auxiliary-playlist-item-img" },
].some((rule) => {
const container = document.querySelector(rule.container);
if (container) {
const list = [...(container?.children ?? [])];
const curIndex = list.findIndex((e) => e.matches(rule.cur) || e.querySelector(rule.cur));
const sibling = list[curIndex + delta];
const target = sibling?.matches(rule.target) ? sibling : sibling?.querySelector(rule.target);
if (curIndex === -1 || !target) return false;
// @ts-ignore
return target.click(), true;
}
});
};
// * ---------------------------------------------------------------- danmu
let danmuShown = true;
const toggleDanmu = () => {
danmuShown = !danmuShown;
// 1,5, others
const danmuButton = document.querySelectorAll(".bpx-player-dm-switch, .bilibili-player-video-danmaku-switch");
const danmuLayer = document.querySelectorAll(".bpx-player-row-dm-wrap, .bpx-player-adv-dm-wrap, .bpx-player-bas-dm-wrap, .bpx-player-cmd-dm-wrap, .bilibili-player-video-danmaku");
// @ts-ignore
[...danmuButton, ...danmuLayer].forEach((e) => (e.style.opacity = danmuShown ? 1 : 0));
messager(danmuShown ? "弹幕开" : "弹幕关");
};
// * ---------------------------------------------------------------- fullscreen
const toggleWebFull = () => {
// @ts-ignore
document.querySelector(".bpx-player-ctrl-web, .squirtle-video-pagefullscreen, .bilibili-player-video-web-fullscreen")?.click();
};
const toggleScreenFull = () => {
// @ts-ignore
document.querySelector(".bpx-player-ctrl-full, .squirtle-video-fullscreen, .bilibili-player-video-btn-fullscreen")?.click();
};
// * ---------------------------------------------------------------- time jump
const jumpStep = 2;
const inRange = (v, [min, max]) => Math.min(Math.max(v, min), max);
// * ----------------
const setPlaybackJumpToPercent = (t) => {
const video = getBiliVideo();
if (!video) return;
// * patch
if (Number(t) === 0) return (video.currentTime = 0.35);
video.currentTime = (video.duration / 10) * Number(t);
};
// * ----------------
const setPlaybackJumpBySec = (delta) => {
const video = getBiliVideo();
if (!video) return;
video.currentTime = inRange(video.currentTime + delta, [0, video.duration]);
};
// * ----------------
const togglePlay = () => {
const video = getBiliVideo();
if (!video) return;
video.paused ? video.play() : video.pause();
};
// * ---------------------------------------------------------------- speed control
const speedStep = 0.15;
const speedRange = [0.05, 12];
const favSpeed = 1.75;
let localSpeed = 1;
const setPlaybackSpeedBy = (delta) => {
const video = getBiliVideo();
if (!video) return;
// @ts-ignore
localSpeed = inRange(video.playbackRate + delta, speedRange);
video.playbackRate = localSpeed;
showPlaybackSpeed();
};
const togglePlaybackSpeed = () => {
const video = getBiliVideo();
if (!video) return;
const curSpeed = video.playbackRate;
video.playbackRate = curSpeed === 1 ? (localSpeed === 1 ? favSpeed : localSpeed) : 1;
showPlaybackSpeed();
};
const showPlaybackSpeed = () => {
const curSpeed = getBiliVideo()?.playbackRate;
curSpeed && messager("倍速 " + curSpeed.toFixed(2).replace(/\.?0+$/, ""));
};
// * ---------------------------------------------------------------- replay loop
const setReplayLoop = () => {
const input = document.querySelector(".bpx-player-ctrl-setting-loop input, input.squirtle-setting-loop, .bilibili-player-video-btn-setting-left-repeat input");
messager("洗脑循环");
// @ts-ignore
// * already looped
if (input?.checked === true) return;
// @ts-ignore
// * turn on loop
input?.click();
// * if finished, start replay immediately
const video = getBiliVideo();
if (!video) return;
if (video.paused && video.currentTime >= video.duration) {
setPlaybackJumpToPercent(0);
video.play();
}
};
// * ---------------------------------------------------------------- helper indicator messager
let indicatorTimer = -1;
const messager = (text) => {
// * ---------------- prepare indicator
const wrap = document.querySelector(".bpx-player-video-wrap, .bilibili-player-video-wrap");
if (!wrap) return null;
let indicator = document.querySelector("#mod-messager");
if (!indicator) {
indicator = document.createElement("div");
indicator.id = "mod-messager";
wrap.appendChild(indicator);
}
// * ---------------- update indicator
indicator.textContent = text;
indicator.classList.add("shown");
clearTimeout(indicatorTimer);
indicatorTimer = setTimeout(() => indicator?.classList.remove("shown"), 1000);
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment