-
-
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/* | |
// @match https://music.youtube.com/* | |
// @grant GM_addElement | |
// @version 2.73beta | |
// @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 + "' }"; | |
curStyle.textContent += " ytmusic-nerd-stats::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 regular associated volume panel"); | |
volumePanel = document.querySelector("ytmusic-player-bar #volume-slider #sliderBar") | |
if(!volumePanel) { | |
logMessage("no associated music volume panel either"); | |
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)); |
Can you add support for YT Music?
Sure. The latest version should now work with the Music site. Try it and let me know if you have any issues.
It is working now, thanks!
Thank you so much man Blocktube's implementation suddenly broke and only applied if I manually reloaded the page. Now I can go back to listening to music at it's normal volume again.
Any chance on issuing an update to this script regarding YT's new player UI? Recently, YouTube is giving me this newer look of their player, therefore the script doesn't work anymore. This is usually my go-to for a while now since other scripts don't work for me for some reason. Looking forward to an update :)
Any chance on issuing an update to this script regarding YT's new player UI? Recently, YouTube is giving me this newer look of their player, therefore the script doesn't work anymore. This is usually my go-to for a while now since other scripts don't work for me for some reason. Looking forward to an update :)
WHAT IN THE HOLY BLASPHEMOUS DEMON is that UI? F*** right off with that shit, Youtube..
The new UI is terrible, but until this script is updated, YTM works, and I have no issues with ads with my UBlockPlus setup. I majorly use this for music, so.
Sure. The latest version should now work with the Music site. Try it and let me know if you have any issues.