Skip to content

Instantly share code, notes, and snippets.

@rxliuli
Last active September 21, 2025 14:58
Show Gist options
  • Save rxliuli/a5aec5edd7070b7f38112665567931bf to your computer and use it in GitHub Desktop.
Save rxliuli/a5aec5edd7070b7f38112665567931bf to your computer and use it in GitHub Desktop.
Keep YouTube Music playing in background
// ==UserScript==
// @name YouTube Music Background Play
// @namespace https://rxliuli.com
// @version 0.1.1
// @description Keep YouTube Music playing in background
// @match https://music.youtube.com/*
// @sandbox DOM
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
class Vista {
constructor(interceptors = []) {
this.interceptors = interceptors;
}
middlewares = [];
cancels = [];
use(middleware) {
this.middlewares.push(middleware);
return this;
}
intercept() {
this.cancels = this.interceptors.map(
(interceptor) => interceptor(this.middlewares)
);
}
destroy() {
this.cancels.forEach((cancel) => cancel());
this.cancels = [];
}
}
async function handleRequest(context, middlewares) {
const compose = (i) => {
if (i >= middlewares.length) {
return Promise.resolve();
}
return middlewares[i](context, () => compose(i + 1));
};
await compose(0);
}
const interceptEvent = (middlewares, options) => {
const originalAddEventListener = EventTarget.prototype.addEventListener;
const originalRemoveEventListener = EventTarget.prototype.removeEventListener;
const wrappedListeners = options?.wrappedListeners ?? [];
EventTarget.prototype.addEventListener = function(type, listener, options2) {
if (!listener) {
console.warn("Event listener is null or undefined");
return;
}
const wrappedListener = function(event) {
return handleRequest({ type: "event", event }, [
...middlewares,
(c) => typeof listener === "function" ? listener?.(c.event) : listener?.handleEvent(c.event)
]);
};
wrappedListeners.push({
original: listener,
dom: this,
listener: wrappedListener,
type,
options: options2
});
return originalAddEventListener.call(this, type, wrappedListener, options2);
};
EventTarget.prototype.removeEventListener = function(type, listener, options2) {
if (!listener) {
console.warn("Event listener is null or undefined");
return;
}
const listeners = wrappedListeners.filter(
(it) => it.dom === this && it.type === type && it.original === listener && it.options === options2
);
if (listeners.length === 0) {
console.warn("Event listener is not wrapped, cannot remove listener");
return;
}
listeners.forEach((it) => {
originalRemoveEventListener.call(this, type, it.listener, options2);
wrappedListeners.splice(wrappedListeners.indexOf(it), 1);
});
};
return () => {
wrappedListeners.forEach((it) => {
originalRemoveEventListener.call(it.dom, it.type, it.listener, it.options);
originalAddEventListener.call(it.dom, it.type, it.original, it.options);
});
wrappedListeners.length = 0;
EventTarget.prototype.addEventListener = originalAddEventListener;
EventTarget.prototype.removeEventListener = originalRemoveEventListener;
};
};
function nonStop() {
Object.defineProperty(document, "hidden", {
get: () => false,
configurable: true
});
Object.defineProperty(document, "visibilityState", {
get: () => "visible",
configurable: true
});
document.hasFocus = () => true;
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeName === "YTMUSIC-YOU-THERE-RENDERER" || node instanceof HTMLElement && node.querySelector("ytmusic-you-there-renderer")) {
console.log('Auto-closing "Are you there?" dialog');
setTimeout(() => {
const yesButton = document.querySelector(
'ytmusic-you-there-renderer button[aria-label="Yes"]'
);
if (yesButton instanceof HTMLButtonElement) {
yesButton.click();
console.log('Clicked "Yes" button');
}
}, 100);
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
new Vista([interceptEvent]).use(async (c, next) => {
if (c.event.target instanceof HTMLVideoElement || c.event.target instanceof HTMLAudioElement || c.event.target instanceof HTMLImageElement || c.event.target instanceof XMLHttpRequest || c.event.target instanceof IDBRequest || c.event.target instanceof IDBTransaction || c.event.target instanceof SourceBuffer) {
return await next();
}
if (c.event.target === window || c.event.target === document) {
if ([
"focus",
"focusin",
"pageshow",
"visibilitychange",
"mouseenter",
"mouseover",
"mousemove"
].includes(c.event.type)) {
console.log("Blocked event listener:", c.event.target, c.event.type);
return;
}
}
if (![
"mousemove",
"mouseover",
"mouseout",
"pointermove",
"pointerout",
"pointerover"
].includes(c.event.type)) {
console.log("Event:", c.event.type, c.event.target);
}
await next();
}).intercept();
}
nonStop();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment