A small script witten by google's gemini to save audio volume levels per channel on twitch.
// ==UserScript==
// @name Twitch Volume Memory - Ultimate Sync
// @namespace http://tampermonkey.net/
// @version 1.6
// @description A small script witten by google's gemini to save audio volume levels per channel on twitch.
// @author Gemini
// @match https://www.twitch.tv/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let lastUrl = location.href;
// Helper to show the purple notification
const showNotify = (msg) => {
let notify = document.getElementById('vol-mem-notify');
if (!notify) {
notify = document.createElement('div');
notify.id = 'vol-mem-notify';
notify.style = "position:fixed; top:80px; right:20px; background:rgba(145,71,255,0.9); color:white; padding:10px 15px; border-radius:5px; z-index:9999; font-weight:bold; pointer-events:none; transition:opacity 0.5s; font-family: sans-serif;";
document.body.appendChild(notify);
}
notify.innerText = msg;
notify.style.opacity = "1";
setTimeout(() => { notify.style.opacity = "0"; }, 2500);
};
// Helper to bypass React's internal state
function setNativeValue(element, value) {
const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set;
const prototype = Object.getPrototypeOf(element);
const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
if (valueSetter && valueSetter !== prototypeValueSetter) {
prototypeValueSetter.call(element, value);
} else {
valueSetter.call(element, value);
}
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
}
const getStreamer = () => {
const path = window.location.pathname.split('/');
const invalid = ["", "directory", "search", "settings", "videos", "u"];
return path[1] && !invalid.includes(path[1]) ? path[1] : null;
};
const applyVolume = () => {
const streamer = getStreamer();
const slider = document.querySelector('input[data-a-target="player-volume-slider"]');
const saved = localStorage.getItem(`vol_${streamer}`);
if (slider && saved !== null && streamer) {
setNativeValue(slider, saved);
showNotify(`🔊 ${streamer}: ${Math.round(saved * 100)}%`);
}
};
// Save volume when the slider is moved
document.addEventListener('input', (e) => {
if (e.target.getAttribute('data-a-target') === 'player-volume-slider') {
const streamer = getStreamer();
if (streamer) {
localStorage.setItem(`vol_${streamer}`, e.target.value);
}
}
}, true);
const observer = new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
setTimeout(applyVolume, 2000); // Wait for React to load player
}
const slider = document.querySelector('input[data-a-target="player-volume-slider"]');
if (slider && !slider.dataset.volSet) {
applyVolume();
slider.dataset.volSet = "true";
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();