Skip to content

Instantly share code, notes, and snippets.

@jamesliu96
Last active April 11, 2024 06:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesliu96/03775eba64ff6b26efd6ee2676354f52 to your computer and use it in GitHub Desktop.
Save jamesliu96/03775eba64ff6b26efd6ee2676354f52 to your computer and use it in GitHub Desktop.
Bilibili Ambient Mode
// ==UserScript==
// @name BAMBIENT
// @version 1.1.3
// @description Bilibili Ambient Mode
// @author jamesliu96
// @license MIT
// @namespace https://jamesliu.info/
// @homepage https://gist.github.com/jamesliu96/03775eba64ff6b26efd6ee2676354f52
// @match https://*.bilibili.com/*
// @icon https://www.bilibili.com/favicon.ico
// @connect bilibili.com
// ==/UserScript==
const BLUR_RADIUS = 48; // px
const DURATION = 5000; // ms
const DELAY = 0; // ms
const RETRY_DURATION = 1000; // ms
/** @param {number} t */
const sleep = (t) => new Promise((r) => setTimeout(r, t));
/** @param {string} name */
const safeGet = (name) => {
// eslint-disable-next-line no-undef
if (!player || !(name in player)) return;
// eslint-disable-next-line no-undef
return player[name];
};
/** @param {string} name */
const safeInvoke = (name, ...args) => {
try {
const x = safeGet(name);
if (typeof x === 'function') return x(...args);
} catch {}
};
const safeGetVideo = () => {
const v = safeInvoke('mediaElement');
if (v instanceof HTMLVideoElement) return v;
};
/**
* @param {HTMLElement} startExc
* @param {HTMLElement|null|undefined} endInc
*/
const cleanse = (startExc, endInc) => {
for (
let cur = startExc.parentElement;
cur && cur.parentElement !== endInc;
cur = cur.parentElement
) {
const cs = getComputedStyle(cur);
if (cs.getPropertyValue('box-shadow') !== 'none') {
cur.style.setProperty('box-shadow', 'none', 'important');
}
if (cs.getPropertyValue('overflow') !== 'visible') {
cur.style.setProperty('overflow', 'visible', 'important');
}
}
};
/** @param {HTMLVideoElement} video */
const snapshot = async (video) => {
const url = await safeInvoke('readFrameAsDataURL');
if (typeof url === 'string' && url !== 'data:,') return url;
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d')?.drawImage(video, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL();
};
addEventListener('load', async () => {
for (;;) {
/** @type {HTMLElement|null} */
const videoPlayer =
document.querySelector('#bilibili-player') ??
document.querySelector('#live-player');
const video = safeGetVideo() ?? videoPlayer?.querySelector('video');
if (
!(videoPlayer instanceof HTMLElement) ||
!(video instanceof HTMLVideoElement)
) {
await sleep(RETRY_DURATION);
continue;
}
/** @type {HTMLElement|null} */
let bambient = videoPlayer.querySelector('#bambient');
if (!(bambient instanceof HTMLElement)) {
bambient = document.createElement('div');
videoPlayer.prepend(bambient);
bambient.id = 'bambient';
bambient.style.userSelect = 'none';
bambient.style.pointerEvents = 'none';
bambient.style.position = 'absolute';
bambient.style.top =
bambient.style.right =
bambient.style.bottom =
bambient.style.left =
'0px';
bambient.style.filter = `blur(${BLUR_RADIUS}px)`;
}
safeInvoke('setBlackGap', false);
cleanse(bambient, videoPlayer);
/** @type {HTMLElement|null} */
const sendingArea = videoPlayer.querySelector('.bpx-player-sending-area');
bambient.style.marginBottom = `${sendingArea?.offsetHeight ?? 0}px`;
/** @type {[HTMLImageElement|undefined,HTMLImageElement|undefined]} */
let [cinematic0, cinematic1] = bambient.children;
if (
!(cinematic0 instanceof HTMLImageElement) ||
!(cinematic1 instanceof HTMLImageElement)
) {
[cinematic0, cinematic1] = [new Image(), new Image()];
bambient.replaceChildren(cinematic0, cinematic1);
cinematic0.style.position = cinematic1.style.position = 'absolute';
cinematic0.style.width =
cinematic0.style.height =
cinematic1.style.width =
cinematic1.style.height =
'100%';
cinematic0.style.objectFit = cinematic1.style.objectFit = 'contain';
cinematic1.style.transition = `opacity ${DURATION}ms`;
}
if (!cinematic0.src || !cinematic1.src) {
cinematic0.src = cinematic1.src = await snapshot(video);
} else {
if (cinematic1.style.opacity === '0') {
cinematic1.src = await snapshot(video);
cinematic1.style.opacity = '1';
} else {
cinematic0.src = await snapshot(video);
cinematic1.style.opacity = '0';
}
}
await sleep(DURATION + DELAY);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment