Kick Volume Wheel Control

Use the mouse wheel to adjust the volume. Middle-click to mute or unmute the player. Volume slider always visible.

// ==UserScript==
// @name         Kick Volume Wheel Control
// @namespace    https://github.com/pabli24
// @version      1.0.2
// @description  Use the mouse wheel to adjust the volume. Middle-click to mute or unmute the player. Volume slider always visible.
// @author       Pabli
// @license      MIT
// @match        https://kick.com/*
// @icon         data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4wIiB3aWR0aD0iNTAwIiBoZWlnaHQ9IjUwMCIgdmlld0JveD0iMCAwIDc1IDc1Ij4NCjxwYXRoIGQ9Ik0zOS4zODksMTMuNzY5IEwyMi4yMzUsMjguNjA2IEw2LDI4LjYwNiBMNiw0Ny42OTkgTDIxLjk4OSw0Ny42OTkgTDM5LjM4OSw2Mi43NSBMMzkuMzg5LDEzLjc2OXoiIHN0eWxlPSJzdHJva2U6IzUzZmMxODtzdHJva2Utd2lkdGg6NTtzdHJva2UtbGluZWpvaW46cm91bmQ7ZmlsbDojNTNmYzE4OyIvPg0KPHBhdGggZD0iTTQ4LDI3LjZhMTkuNSwxOS41IDAgMCAxIDAsMjEuNE01NS4xLDIwLjVhMzAsMzAgMCAwIDEgMCwzNS42TTYxLjYsMTRhMzguOCwzOC44IDAgMCAxIDAsNDguNiIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzUzZmMxODtzdHJva2Utd2lkdGg6NTtzdHJva2UtbGluZWNhcDpyb3VuZCIvPg0KPC9zdmc+
// @grant        none
// ==/UserScript==

(function() {
	"use strict";

const CONFIG = {
	VOLUME_STEP: 1,
	SHOW_CONTROLS_ON_SCROLL: true,
	SLIDER_ALWAYS_VISIBLE: true,
	HIDE_CURSOR_DELAY: 4000
};

function setCookie(name, value, days) {
	const date = new Date();
	date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
	document.cookie = `${name}=${value}; expires=${date.toUTCString()}; path=/`;
}

function prevent(e) {
	e.preventDefault();
	e.stopPropagation();
	e.stopImmediatePropagation();
}

const observer = new MutationObserver(mutations => {
	const video = document.getElementById("video-player");
	if (!video) return;
	wheel(video);
});
observer.observe(document.body, { childList: true, subtree: true });

function wheel(video) {
	const videoDiv = document.querySelector("#injected-embedded-channel-player-video > div");
	if (videoDiv.hasAttribute("kpvolume")) return;
	videoDiv.setAttribute("kpvolume", "");

	videoDiv.addEventListener("wheel", (event) => {
		prevent(event);
		
		if (CONFIG.SHOW_CONTROLS_ON_SCROLL === true) {
			const showEvent = new Event('mousemove');
			videoDiv.dispatchEvent(showEvent);
		}
		
		if (video.muted && videoDiv.getAttribute("kpvolume")) {
			video.muted = false;
			setTimeout(() => {
				video.volume = videoDiv.getAttribute("kpvolume");
				slider();
			}, 50)
		} else if (event.deltaY < 0) {
			video.volume = Math.min(1, video.volume + (CONFIG.VOLUME_STEP / 100)); // Increase volume
		} else if (event.deltaY > 0) {
			video.volume = Math.max(0, video.volume - (CONFIG.VOLUME_STEP / 100)); // Decrease volume
		}

		setTimeout(slider, 50);
		setTimeout(() => setCookie("volume", video.volume, 365), 3000);
	});
	
	let hideCursorTimeout;
	videoDiv.addEventListener("mousemove", (event) => {
		setTimeout(() => {
			muteBtn();
			slider();
		}, 50)
		setTimeout(() => setCookie("volume", video.volume, 365), 3000);
		
		if (videoDiv.contains(event.target)) {
			videoDiv.style.cursor = 'default';
			if (hideCursorTimeout) clearTimeout(hideCursorTimeout);
			
			hideCursorTimeout = setTimeout(() => {
				videoDiv.style.cursor = 'none';
			}, CONFIG.HIDE_CURSOR_DELAY);
		}
	});
	videoDiv.addEventListener("mouseenter", (event) => {
		setTimeout(() => setCookie("volume", video.volume, 365), 100);
	});

	function slider() {
		const controls = videoDiv.querySelector('div > div.z-controls');
		if (!controls) return;
		const sliderFill = controls.querySelector('span[style*="right:"]'); // style="left: 0%; right: 40%;"

		const videoVolume = Math.round(video.volume * 100);
		sliderFill.style.right = `${100 - videoVolume}%`;

		const sliderThumb = controls.querySelector('span[style*="transform: var(--radix-slider-thumb-transform)"]'); // left: calc(40% + 1.6px);
		const offset = 8 + (videoVolume / 100) * -16;
		sliderThumb.style.left = `calc(${videoVolume}% + ${offset}px)`;

		const sliderValuenow = controls.querySelector('span[aria-valuenow]'); // aria-valuenow="40"
		sliderValuenow.setAttribute("aria-valuenow", videoVolume);

		const sliderP = controls.querySelector('.group\\/volume .betterhover\\:group-hover\\/volume\\:flex');
		sliderP.setAttribute("playervolume", videoVolume + "%");
	}

	function muteBtn() {
		const muteButton = document.querySelector('#injected-embedded-channel-player-video > div > div.z-controls .group\\/volume > button');
		if (!muteButton) return;
		muteButton.addEventListener("click", (event) => {
			prevent(event);
			mute();
		});
	}
	function mute() {
		if (video.muted) {
			video.muted = false;
			setTimeout(() => {
				video.volume = videoDiv.getAttribute("kpvolume");
				slider();
			}, 50)
		} else {
			videoDiv.setAttribute("kpvolume", video.volume);
			video.muted = true;
		}
	}
	videoDiv.addEventListener("mousedown", ({ button }) => {
		if (event.button === 1) {
			prevent(event);
			mute();
		}
	});
	document.addEventListener("keydown", (event) => {
		if ((event.key === 'M' || event.key === 'm') && event.target.tagName !== 'INPUT' && event.target.tagName !== 'TEXTAREA' && event.target.isContentEditable !== true) {
			prevent(event);
			mute();
		}
	});
}

let styles = `
#injected-embedded-channel-player-video > div > div.z-controls .group\\/volume .betterhover\\:group-hover\\/volume\\:flex::after {
	content: attr(playervolume);
	font-weight: 600;
	font-size: .875rem;
	line-height: 1.25rem;
	margin-left: .5rem;
	width: 4ch;
}`
if (CONFIG.SLIDER_ALWAYS_VISIBLE === true) {
	styles += `
#injected-embedded-channel-player-video > div > div.z-controls .group\\/volume .betterhover\\:group-hover\\/volume\\:flex {
	display: flex;
	align-items: center;
}`
}
const styleSheet = document.createElement("style");
styleSheet.textContent = styles;
document.head.appendChild(styleSheet);

})();