-
-
Save abec2304/2782f4fc47f9d010dfaab00f25e69c8a to your computer and use it in GitHub Desktop.
// ==UserScript== | |
// @name No YouTube Volume Normalization | |
// @namespace https://gist.github.com/abec2304 | |
// @match https://www.youtube.com/* | |
// @grant GM_addElement | |
// @version 2.72 | |
// @author abec2304 | |
// @description Enjoy YouTube videos at their true volume | |
// @run-at document-start | |
// @allFrames true | |
// ==/UserScript== | |
/* eslint-env browser, greasemonkey */ | |
(function xvolnorm(pageScript, thisObj) { | |
"use strict"; | |
var scriptId = "ytvolfix2"; | |
var logMessage = function(message) { | |
console.debug(scriptId + "_injector: " + message); | |
}; | |
var digestMessage = function(message, callback) { | |
var msgBytes = new TextEncoder().encode(message); | |
logMessage("attempting to hash script"); | |
window.crypto.subtle.digest("SHA-256", msgBytes).then(function(buffer) { | |
var arr; | |
var hex; | |
if(typeof cloneInto !== typeof undefined) { | |
// workaround for Firemonkey | |
buffer = cloneInto(buffer, thisObj); | |
} | |
try { | |
arr = Array.from(new Uint8Array(buffer)); | |
hex = arr.map(function(b) { | |
return b.toString(16).padStart(2, "0"); | |
}).join(""); | |
logMessage("obtained hash"); | |
callback(hex); | |
} catch(_ignore) { | |
logMessage("unable to convert hash data"); | |
callback("unknown"); | |
} | |
}); | |
}; | |
var inject = function(hash) { | |
var content = "(" + pageScript + ")('" + scriptId + "', '" + hash + "');"; | |
logMessage("preparing page script"); | |
if(document.head) { | |
GM_addElement("script", {id: scriptId, textContent: content}); | |
logMessage("injected page script"); | |
return; | |
} | |
document.addEventListener("DOMContentLoaded", function() { | |
GM_addElement("script", {id: scriptId, textContent: content}); | |
logMessage("injected page script (delayed)"); | |
}); | |
}; | |
if(typeof GM_addElement === typeof undefined) { | |
window.GM_addElement = function(a, b) { | |
var elem = document.createElement(a); | |
Object.keys(b).forEach(function(key) { | |
elem[key] = b[key]; | |
}); | |
document.head.appendChild(elem); | |
return elem; | |
}; | |
logMessage("defined addElement polyfill"); | |
} | |
try { | |
digestMessage(pageScript, inject); | |
} catch(_ignore) { | |
logMessage("unable to hash"); | |
inject("unknown"); | |
} | |
}(function(scriptId, hash) { | |
"use strict"; | |
var logMessage = function(message) { | |
console.debug(scriptId + ": " + message); | |
}; | |
var _ignore = logMessage("page script called"); | |
var volumeColors = [ | |
"thistle", | |
"plum", | |
"orchid", | |
"mediumorchid", | |
"darkorchid", | |
"darkviolet" | |
]; | |
var styleNum = 0; | |
var addVolumeStyle = function(parent) { | |
var color = volumeColors[styleNum % volumeColors.length]; | |
var about = "No YouTube Volume Normalization #" + hash.slice(0, 16); | |
var curStyle = parent.querySelector("style." + scriptId + "_style"); | |
if(curStyle) { | |
logMessage("updating style"); | |
} else { | |
curStyle = document.createElement("style"); | |
curStyle.className = scriptId + "_style"; | |
parent.appendChild(curStyle); | |
logMessage("added style element"); | |
} | |
curStyle.textContent = ".ytp-volume-slider-handle::before { background: " + color + "; z-index: -1; }"; | |
curStyle.textContent += " .ytp-sfn-content::after { content: '" + about + "' }"; | |
styleNum += 1; | |
}; | |
var setVolume = function(panel, video, setter) { | |
var newVolume = panel.getAttribute("aria-valuenow") / 100; | |
if(newVolume === video.lastVolume) { | |
return; | |
} | |
video.lastVolume = newVolume; | |
setter.call(video, newVolume); | |
}; | |
var handleVideo = function(videoElem) { | |
var parentL0; | |
var parentL1; | |
var desc; | |
var setter; | |
var volumePanel; | |
parentL0 = videoElem.parentNode; | |
if(!parentL0) { | |
logMessage("video immediately detached from page " + videoElem.outerHTML); | |
return; | |
} | |
parentL1 = parentL0.parentNode; | |
if(!parentL1) { | |
logMessage("video detached from page " + videoElem.outerHTML); | |
return; | |
} | |
desc = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, "volume"); | |
if(!desc) { | |
logMessage("using archaic volume descriptor"); | |
desc = Object.getOwnPropertyDescriptor(videoElem, "volume"); | |
} | |
setter = desc.set; | |
volumePanel = parentL1.querySelector(".ytp-volume-panel"); | |
if(!volumePanel) { | |
logMessage("no associated volume panel"); | |
return; | |
} | |
addVolumeStyle(parentL1); | |
Object.defineProperty(videoElem, "volume", { | |
get: function() { | |
logMessage("read of shadowed volume value"); | |
return 42; | |
}, | |
set: function(_ignore) { | |
var toCall = function() { | |
setVolume(volumePanel, videoElem, setter); | |
}; | |
// slight delay to allow volume panel to update | |
window.setTimeout(toCall, 5); | |
} | |
}); | |
logMessage("shadowed volume property"); | |
setVolume(volumePanel, videoElem, setter); | |
logMessage("initial volume set"); | |
}; | |
var videoObserver; | |
var intervalId; | |
var existingVideos = document.querySelectorAll("video"); | |
logMessage("number of existing video elements = " + existingVideos.length); | |
Array.prototype.forEach.call(existingVideos, handleVideo); | |
videoObserver = new MutationObserver(function(records) { | |
records.forEach(function(mutation) { | |
Array.prototype.forEach.call(mutation.addedNodes, function(node) { | |
if("VIDEO" === node.tagName) { | |
logMessage("observed a video element being added"); | |
handleVideo(node); | |
} | |
}); | |
}); | |
}); | |
videoObserver.observe(document.documentElement, {childList: true, subtree: true}); | |
intervalId = window.setInterval(function ytvolfix2cleanup() { | |
var scriptElem = document.getElementById(scriptId); | |
if(!scriptElem) { | |
logMessage("nothing found to clean up"); | |
} else { | |
scriptElem.parentNode.removeChild(scriptElem); | |
logMessage("cleaned up own script element"); | |
} | |
clearInterval(intervalId); | |
}, 1500); | |
}, this)); |
The only logging done is to check that the script initialized. You could edit the script to add more logging if you want to try identifying where things go wrong.
Hm. I don't know how I'd do that in this language.
Now updated to include extensive logging.
Now updated to include extensive logging.
No kidding! I can get to testing, likely now and tomorrow. How do I check the logs?
Now updated to include extensive logging.
No kidding! I can get to testing, likely now and tomorrow. How do I check the logs?
It will output in the browser console. Open Developer Tools with CTRL SHIFT I
then select Console.
If using Chrome, you may have to click Default levels
at top-right and enable Verbose
.
EDIT: then filter to ytvolfix2
to only see relevant log entries.
No kidding! I can get to testing, likely now and tomorrow. How do I check the logs?
It will output in the browser console. Open Developer Tools with
CTRL SHIFT I
then select Console. If using Chrome, you may have to clickDefault levels
at top-right and enableVerbose
.
Tyvm. I'll get back to you once it doesn't work again. Only thing I've gotten so far is something about a piece of code being deprecated and ignored, instead recommended to use "renderer" over it for Firefox and several errors regarding something called "Cross-Origin", which I'm not sure about, but doesn't seem fairly important to this.
No kidding! I can get to testing, likely now and tomorrow. How do I check the logs?
It will output in the browser console. Open Developer Tools with
CTRL SHIFT I
then select Console. If using Chrome, you may have to clickDefault levels
at top-right and enableVerbose
. EDIT: then filter toytvolfix2
to only see relevant log entries.
Had it happen again. Nothing in the logs at all - Disabling it and re-enabling it fixes it, as previously prescribed. It seemed Tampermonkey may've failed to use it, considering it didn't show it as an active script(though still enabled).
No kidding! I can get to testing, likely now and tomorrow. How do I check the logs?
It will output in the browser console. Open Developer Tools with
CTRL SHIFT I
then select Console. If using Chrome, you may have to clickDefault levels
at top-right and enableVerbose
.Tyvm. I'll get back to you once it doesn't work again. Only thing I've gotten so far is something about a piece of code being deprecated and ignored, instead recommended to use "renderer" over it for Firefox and several errors regarding something called "Cross-Origin", which I'm not sure about, but doesn't seem fairly important to this.
Make sure you have the 'debug' level enabled in order to see the relevant messages.
Also if Tampermonkey is proving unreliable, it might be worth switching to Violentmonkey
Make sure you have the 'debug' level enabled in order to see the relevant messages.
I believe that is enabled, though I'm not sure what the Verbose
thing refers to.
Also if Tampermonkey is proving unreliable, it might be worth switching to Violentmonkey
I'd never heard of that, admittedly, nor did I know other script-adjacent addons existed. Why do you say it'd be worth a switch?
Make sure you have the 'debug' level enabled in order to see the relevant messages.
I believe that is enabled, though I'm not sure what the
Verbose
thing refers to.
Sorry, Verbose is what it's called in Chrome. In Firefox, it's Debug.
Also if Tampermonkey is proving unreliable, it might be worth switching to Violentmonkey
I'd never heard of that, admittedly, nor did I know other script-adjacent addons existed. Why do you say it'd be worth a switch?
I primarily test using Violentmonkey and haven't had any issues.
Sorry, Verbose is what it's called in Chrome. In Firefox, it's Debug.
No worries - Already had that on, as well.
I primarily test using Violentmonkey and haven't had any issues.
I'll give it a go, then. Not sure what difference there is between the two, but I primarily, if only use them for this script, so what's the harm?
Just wanted to give a thank you for this. I hate how websites limit volume. If only this was possible for all media sources on the browser. Not only youtube.
Just wanted to give a thank you for this. I hate how websites limit volume. If only this was possible for all media sources on the browser. Not only youtube.
Any specific sites you have in mind?
Hi, I have a problem with this script, I've developed my own slider that sets the document.querySelector('video').playbackRate from 0.05x-3.00x, the problem is that the range 2.00x-3.00x resets this normalization script which in turn noticeably reloads the video player as it flickers then:
ytvolfix2: observed a video element being added
ytvolfix2: updating style
ytvolfix2: shadowed volume property
ytvolfix2: initial volume set
Those 4 lines are spammed whenever I'm moving in that range and I can notice that the color of the background of the volume slider is being changed gradually to stronger purple shade whenever this happens. Any guess? I'm using latest Librewolf on Linux with only this script, uBlock Origin and I'm just only trying to modify document.querySelector('video').playbackRate beyond 2.00x. I haven't looked at your code just yet, I thought about asking about clues first.
Hi, I have a problem with this script, I've developed my own slider that sets the document.querySelector('video').playbackRate from 0.05x-3.00x, the problem is that the range 2.00x-3.00x resets this normalization script which in turn noticeably reloads the video player as it flickers then:
ytvolfix2: observed a video element being added ytvolfix2: updating style ytvolfix2: shadowed volume property ytvolfix2: initial volume set
Those 4 lines are spammed whenever I'm moving in that range and I can notice that the color of the background of the volume slider is being changed gradually to stronger purple shade whenever this happens. Any guess? I'm using latest Librewolf on Linux with only this script, uBlock Origin and I'm just only trying to modify document.querySelector('video').playbackRate beyond 2.00x. I haven't looked at your code just yet, I thought about asking about clues first.
The volume bar turning more purple is part of my script, I added that as a visual indicator that unexpected behavior is occurring.
I tried adjusting playbackRate beyond 2x with LibreWolf on Windows and wasn't able to reproduce your issue.
Perhaps LibreWolf on Linux doesn't properly support higher playback rates and causes the video element to be re-initialized.
It kind of fixed itself throughout the week... and I'm not sure what caused this. Probably youtube changed something. Anyway, really thank you for this script. It is an absolute must-have on any firefox based browser when using Linux's audio stack such as pulseaudio or pipewire due to weird bugs, because reported volume levels to system are all out of wack, but you probably know this already.
Can you add support for YT Music?
The only logging done is to check that the script initialized. You could edit the script to add more logging if you want to try identifying where things go wrong.