// ==UserScript==
// @name Kick Latency
// @namespace latency
// @version 1.9.5
// @description Show latency on kick
// @icon https://img.freepik.com/premium-vector/kick-logo-vector-download-kick-streaming-icon-logo-vector-eps_691560-10811.jpg
// @author frz
// @match https://kick.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
let header = null, spinner = null, tooltip = null, container = null, textSpan = null;
const link = document.createElement('link');
link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap';
link.rel = 'stylesheet';
document.head.appendChild(link);
function styleHeader(el) {
Object.assign(el.style, {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
userSelect: 'none'
});
}
function createRedDot() {
const d = document.createElement('span');
Object.assign(d.style, {
display: 'inline-block',
width: '8px',
height: '8px',
borderRadius: '50%',
background: '#FF4B4B',
marginRight: '6px',
transition: 'transform 0.2s ease'
});
return d;
}
function readKickLatency() {
const v = document.querySelector('video');
if (!v || !v.buffered.length) return null;
const lat = v.buffered.end(v.buffered.length - 1) - v.currentTime;
return lat > 0 ? lat.toFixed(2) + 's' : '0.00s';
}
function createSpinner() {
if (spinner) return spinner;
spinner = document.createElement('div');
Object.assign(spinner.style, {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%,-50%)',
zIndex: '9999',
display: 'none'
});
const v = document.querySelector('video');
if (v && v.parentElement) v.parentElement.appendChild(spinner);
return spinner;
}
function reloadPlayer() {
const v = document.querySelector('video');
if (!v) return;
const sp = createSpinner();
sp.style.display = 'block';
const ct = v.currentTime;
v.pause();
setTimeout(() => {
try {
v.currentTime = ct;
v.play().catch(() => {});
} catch {
location.reload();
}
sp.style.display = 'none';
}, 1200);
}
function createTooltip() {
if (tooltip) return;
tooltip = document.createElement('div');
tooltip.textContent = 'Stream delay click to refresh player';
Object.assign(tooltip.style, {
position: 'fixed',
padding: '6px 12px',
fontSize: '13px',
fontWeight: '500',
color: '#fff',
background: 'rgba(0,0,0,0.4)',
backdropFilter: 'blur(6px)',
borderRadius: '6px',
whiteSpace: 'nowrap',
pointerEvents: 'auto',
opacity: '0',
transform: 'translateX(-50%) translateY(10px)',
zIndex: '2147483647'
});
document.body.appendChild(tooltip);
requestAnimationFrame(() => {
tooltip.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
});
}
function showTooltip() {
if (!container) return;
const rect = container.getBoundingClientRect();
tooltip.style.left = rect.left + rect.width / 2 + 'px';
tooltip.style.top = rect.bottom + 8 + 'px';
tooltip.style.transform = 'translateX(-50%) translateY(0)';
tooltip.style.opacity = '1';
container.style.transform = 'translateY(-2px)';
textSpan.style.color = '#fff';
}
function hideTooltip() {
if (!container) return;
tooltip.style.opacity = '0';
tooltip.style.transform = 'translateX(-50%) translateY(10px)';
container.style.transform = 'translateY(0)';
textSpan.style.color = 'rgba(255,255,255,0.85)';
}
function updateHeader() {
if (!header) return;
const lat = readKickLatency();
if (!lat) return;
if (!container) {
container = document.createElement('div');
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.transition = 'transform 0.2s ease';
header.innerHTML = '';
header.appendChild(container);
const dot = createRedDot();
container.appendChild(dot);
textSpan = document.createElement('span');
textSpan.style.color = 'rgba(255,255,255,0.85)';
textSpan.style.fontWeight = '600';
textSpan.style.fontSize = '15px';
textSpan.style.transition = 'color 0.2s ease';
container.appendChild(textSpan);
createTooltip();
}
textSpan.textContent = `Latency: ${lat}`;
}
function findHeader() {
const candidate = Array.from(document.querySelectorAll('span.absolute')).find(e => {
const text = e.textContent.trim().toLowerCase();
return text === 'чат' || text === 'chat';
});
if (candidate && candidate !== header) {
header = candidate;
container = null;
textSpan = null;
styleHeader(header);
header.addEventListener('click', reloadPlayer);
header.addEventListener('mouseenter', showTooltip);
header.addEventListener('mouseleave', hideTooltip);
updateHeader();
}
}
const obs = new MutationObserver(findHeader);
obs.observe(document.body, { childList: true, subtree: true });
setInterval(updateHeader, 2000);
})();