// ==UserScript== // @name Twitch & Kick Latency // @namespace latency // @version 1.3 // @description Displays the Twitch and Kick play latency // @author frz // @match https://www.twitch.tv/* // @match https://kick.com/* // @icon https://www.allkeyshop.com/blog/wp-content/uploads/Twitch-vs-Kick_featured.png // @grant none // @downloadURL none // ==/UserScript== (function () { 'use strict'; let header = null; const platform = location.hostname.includes('kick.com') ? 'kick' : 'twitch'; function styleHeader(el) { el.style.display = 'flex'; el.style.alignItems = 'center'; el.style.justifyContent = 'center'; el.style.color = '#FFFFFF'; el.style.fontWeight = '600'; el.style.fontSize = '15px'; el.style.cursor = 'pointer'; el.style.gap = '6px'; } function createRedDot() { const dot = document.createElement('span'); dot.style.display = 'inline-block'; dot.style.width = '8px'; dot.style.height = '8px'; dot.style.borderRadius = '50%'; dot.style.backgroundColor = '#FF4B4B'; dot.id = 'latency-red-dot'; return dot; } function getLatency() { const video = document.querySelector('video'); if (!video || !video.buffered || video.buffered.length === 0) return null; const latency = video.buffered.end(video.buffered.length - 1) - video.currentTime; return latency > 0 ? latency.toFixed(2) : '0.00'; } function updateText() { if (!header) return; const latency = getLatency(); if (latency === null) return; // Очищаем текст, чтобы убрать "Чат"/"Чат трансляции" header.textContent = ''; let dot = document.getElementById('latency-red-dot'); if (!dot) { dot = createRedDot(); } header.appendChild(dot); const textNode = document.createElement('span'); textNode.className = 'latency-text'; textNode.textContent = `Latency: ${latency}s`; header.appendChild(textNode); } function createPlatformSpinner() { let spinner = document.getElementById('latency-spinner'); if (!spinner) { let spinnerTemplate; if (platform === 'twitch') { spinnerTemplate = document.querySelector('[data-a-target="tw-loading-spinner"]'); } else { spinnerTemplate = document.querySelector('[data-testid="loading-spinner"]'); } spinner = spinnerTemplate ? spinnerTemplate.cloneNode(true) : document.createElement('div'); spinner.id = 'latency-spinner'; spinner.style.position = 'absolute'; spinner.style.top = '50%'; spinner.style.left = '50%'; spinner.style.transform = 'translate(-50%, -50%)'; spinner.style.zIndex = '9999'; spinner.style.display = 'none'; const video = document.querySelector('video'); if (video && video.parentElement) video.parentElement.appendChild(spinner); } return spinner; } function reloadPlayerWithSpinner() { const video = document.querySelector('video'); if (!video) return; const spinner = createPlatformSpinner(); spinner.style.display = 'block'; const currentTime = video.currentTime; video.pause(); setTimeout(() => { try { video.currentTime = currentTime; video.play().catch(() => {}); } catch { location.reload(); } spinner.style.display = 'none'; }, 1200); } function observeHeader() { let candidate; if (platform === 'twitch') { candidate = document.querySelector('#chat-room-header-label'); } else { candidate = Array.from(document.querySelectorAll('span.absolute')).find( el => el.textContent.trim() === 'Чат' ); } if (candidate && candidate !== header) { header = candidate; styleHeader(header); header.addEventListener('click', reloadPlayerWithSpinner); updateText(); } } const observer = new MutationObserver(observeHeader); observer.observe(document.body, { childList: true, subtree: true }); setInterval(updateText, 2000); })();