Skip to content

Instantly share code, notes, and snippets.

@rummik
Created May 21, 2025 19:20
Show Gist options
  • Save rummik/47106741e77d3b91385afbecd2f93a3b to your computer and use it in GitHub Desktop.
Save rummik/47106741e77d3b91385afbecd2f93a3b to your computer and use it in GitHub Desktop.
YouTube Music Volume Slider Fix
// ==UserScript==
// @name YouTube Music Volume Slider Fix
// @version 2025-05-21
// @description Fixes the volume slider to have a reasonable volume range and step size.
// @namespace zick.kim.ytmvsf
// @author *Kim Zick (rummik)
// @license MIT
// @grant none
// @run-at document-end
// @match https://music.youtube.com/*
// @icon https://music.youtube.com/img/favicon_144.png
// ==/UserScript==
{
'use strict';
class SliderFix {
static config = {
min: 0,
max: 20,
// step: 'any',
step: 0.02,
scrollStep: 1,
};
static #sliders = class {
static #elements = document.querySelectorAll('#volume-slider, #expand-volume-slider');
static getAttribute(attr, value) {
return this.#elements.item(0).getAttribute(attr);
}
static setAttribute(attr, value) {
this.#elements.forEach(slider => {
slider.setAttribute(attr, value);
});
switch (attr) {
case 'min': this.setAttribute('aria-valuemin', value); break;
case 'max': this.setAttribute('aria-valuemax', value); break;
case 'value': this.setAttribute('aria-valuenow', value); break;
}
}
static addEventListener(event, callback) {
this.#elements.forEach(slider => {
slider.addEventListener(event, callback);
});
}
static item(item) {
return this.#elements.item(item);
}
};
static setVolume(value) {
let volume = Math.min(Math.max(this.config.min, value), this.config.max);
// This might be fragile. Switch to clicking on the center of the slider's child element?
// Seems like we can use .slider-knob-inner, or #sliderKnob
const sliderBar = this.#sliders.item(0).querySelector('#sliderBar');
const rect = sliderBar.getBoundingClientRect();
const click = new MouseEvent('click', {
clientX: rect.x + rect.width * ((volume - this.config.min) / (this.config.max - this.config.min)),
clientY: rect.y + rect.height / 2,
});
this.#sliders.setAttribute('value', volume);
sliderBar.dispatchEvent(click);
}
static getVolume() {
return parseFloat(
this.#sliders.getAttribute('value') ||
(this.config.min + (this.config.max - this.config.min) / 2)
);
}
static {
this.#sliders.setAttribute('min', this.config.min);
this.#sliders.setAttribute('max', this.config.max);
this.#sliders.setAttribute('step', this.config.step);
// if (Math.min(this.getVolume(), this.config.max) % this.config.max > this.config.min) {
const volume = this.getVolume();
if (volume > this.config.max || volume < this.config.min) {
this.setVolume(volume);
}
this.#sliders.addEventListener('wheel', event => {
event.preventDefault();
event.stopPropagation();
const delta = event.deltaX ? event.deltaX : -event.deltaY;
const step = Math.sign(delta) * this.config.scrollStep;
this.setVolume(this.getVolume() + step);
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment