// ==UserScript== // @name Twitch & Kick Latency // @namespace latency // @version 1.5.3 // @description Displays Twitch & Kick latency with proper player reload // @author frz // @icon https://www.allkeyshop.com/blog/wp-content/uploads/Twitch-vs-Kick_featured.png // @match https://www.twitch.tv/* // @match https://kick.com/* // @grant none // @downloadURL none // ==/UserScript== (function(){ const pad=10,k='miniPlayerPos',sK='miniPlayerSize',dK='miniPlayerDraggable'; const platform=location.hostname.includes('kick.com')?'kick':'twitch'; let header=null,spinner=null,miniPlayer=null,gearBtn=null,menu=null; function getHeaderHeight(){ const sel=['.top-nav','[data-a-target="top-nav"]','.top-nav__menu','header','.tw-header']; for(let s of sel){let e=document.querySelector(s);if(e){let r=e.getBoundingClientRect();if(r.height>0)return r.bottom+pad;}} return 80+pad; } function styleHeader(el){ Object.assign(el.style,{display:'flex',alignItems:'center',justifyContent:'center',color:'#fff',fontWeight:'600',fontSize:'15px',cursor:'pointer',gap:'6px'}); } function createRedDot(){ const d=document.createElement('span'); d.id='latency-red-dot'; Object.assign(d.style,{display:'inline-block',width:'8px',height:'8px',borderRadius:'50%',background:'#FF4B4B'}); return d; } async function readTwitchStats(timeoutMs=1500){ const existing=document.querySelector('p[aria-label="Задержка до владельца канала"]'); if(existing)return existing.textContent.trim(); const toggle=()=>{['keydown','keyup'].forEach(t=>document.dispatchEvent(new KeyboardEvent(t,{ctrlKey:true,altKey:true,shiftKey:true,code:'KeyS',key:'S',bubbles:true,cancelable:true})));}; try{toggle();}catch{} const start=Date.now(); while(Date.now()-startsetTimeout(r,150)); } try{toggle();}catch{} return null; } 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'; } async function getLatency(){ if(platform==='kick')return readKickLatency(); let val=await readTwitchStats(); if(!val){const v=document.querySelector('video');if(v&&v.buffered.length){let l=v.buffered.end(v.buffered.length-1)-v.currentTime;return l>0?l.toFixed(2)+'s':'0.00s';}return null;} const m=val.match(/([\d,.]+)\s*(сек|s|ms)?/i); if(m&&m[1]){let num=parseFloat(m[1].replace(',','.'));if(m[2]&&/ms/i.test(m[2]))num/=1e3;return num.toFixed(2)+'s';} return val; } async function updateHeader(){ if(!header)return; const lat=await getLatency(); if(!lat)return; header.innerHTML=''; let dot=document.getElementById('latency-red-dot');if(!dot)dot=createRedDot(); header.appendChild(dot); const s=document.createElement('span');s.textContent=`Latency: ${lat}`; header.appendChild(s); } function createSpinner(){ if(spinner)return spinner; spinner=document.createElement('div'); spinner.id='latency-spinner'; 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';updateHeader();},1200); } function findHeader(){ let candidate=platform==='twitch'?document.querySelector('#chat-room-header-label'):Array.from(document.querySelectorAll('span.absolute')).find(e=>e.textContent.trim()==='Чат'); if(candidate&&candidate!==header){header=candidate;styleHeader(header);header.addEventListener('click',reloadPlayer);updateHeader();} } const obs=new MutationObserver(findHeader); obs.observe(document.body,{childList:true,subtree:true}); setInterval(()=>{if(header)updateHeader();},2000); function initializeMiniPlayer(){ miniPlayer=document.querySelector('.persistent-player__border--mini'); const player=document.querySelector('.persistent-player'); if(!miniPlayer||!player)return false; function setDefaultPosition(){ let h=getHeaderHeight(); miniPlayer.style.left=pad+'px'; miniPlayer.style.top=h+'px'; miniPlayer.style.bottom='auto'; miniPlayer.style.right='auto'; } let saved=localStorage.getItem(k); if(saved){ try{ let pos=JSON.parse(saved); let h=getHeaderHeight(); let maxL=window.innerWidth-miniPlayer.offsetWidth-pad; let maxT=window.innerHeight-miniPlayer.offsetHeight-pad; let safeTop=Math.max(h,Math.min(pos.top,maxT)); miniPlayer.style.left=Math.max(pad,Math.min(pos.left,maxL))+'px'; miniPlayer.style.top=safeTop+'px'; }catch{setDefaultPosition();} }else setDefaultPosition(); let savedSize=parseFloat(localStorage.getItem(sK))||1; miniPlayer.style.transform=`scale(${savedSize})`; if(miniPlayer._dragInitialized)return true; Object.assign(miniPlayer.style,{position:'fixed',cursor:'move',zIndex:'9999',margin:'0'}); addGearToMiniPlayer(); let dragging=false,startX=0,startY=0,initLeft=0,initTop=0; miniPlayer.addEventListener('mousedown',e=>{ const draggable=localStorage.getItem(dK)!=='false'; if(!draggable||e.target.closest('.mini-gear'))return; dragging=true; startX=e.clientX;startY=e.clientY; const r=miniPlayer.getBoundingClientRect(); initLeft=r.left;initTop=r.top; miniPlayer.style.transition='none'; document.addEventListener('mousemove',drag); document.addEventListener('mouseup',stopDrag); e.preventDefault(); }); function drag(e){ if(!dragging)return; let dx=e.clientX-startX,dy=e.clientY-startY; let newL=Math.max(pad,Math.min(initLeft+dx,window.innerWidth-miniPlayer.offsetWidth-pad)); let newT=Math.max(getHeaderHeight(),Math.min(initTop+dy,window.innerHeight-miniPlayer.offsetHeight-pad)); miniPlayer.style.left=newL+'px'; miniPlayer.style.top=newT+'px'; } function stopDrag(){ if(!dragging)return; dragging=false; document.removeEventListener('mousemove',drag); document.removeEventListener('mouseup',stopDrag); const r=miniPlayer.getBoundingClientRect(); localStorage.setItem(k,JSON.stringify({left:Math.round(r.left),top:Math.round(r.top)})); miniPlayer.style.transition=''; } window.addEventListener('resize',()=>{ let r=miniPlayer.getBoundingClientRect(); let newL=Math.max(pad,Math.min(r.left,window.innerWidth-miniPlayer.offsetWidth-pad)); let newT=Math.max(getHeaderHeight(),Math.min(r.top,window.innerHeight-miniPlayer.offsetHeight-pad)); if(newL!==r.left||newT!==r.top){miniPlayer.style.left=newL+'px';miniPlayer.style.top=newT+'px';} }); miniPlayer._dragInitialized=true; return true; } function addGearToMiniPlayer(){ document.querySelectorAll('.mini-gear').forEach(e=>e.remove()); if(!miniPlayer||!miniPlayer.classList.contains('persistent-player__border--mini'))return; if(miniPlayer.querySelector('.mini-gear'))return; gearBtn=document.createElement('button'); gearBtn.className='mini-gear'; Object.assign(gearBtn.style,{position:'absolute',top:'8px',right:'40px',background:'rgba(0,0,0,0.5)',border:'none',borderRadius:'4px',width:'24px',height:'24px',cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',zIndex:'99999',opacity:'0',transition:'opacity 0.15s ease-in-out'}); gearBtn.innerHTML=``; miniPlayer.appendChild(gearBtn); miniPlayer.addEventListener('mouseenter',()=>{if(gearBtn)gearBtn.style.opacity='1';}); miniPlayer.addEventListener('mouseleave',()=>{if(gearBtn)gearBtn.style.opacity='0';}); gearBtn.addEventListener('click',toggleMenu); } function createMenu(){ if(menu)return; menu=document.createElement('div'); Object.assign(menu.style,{position:'fixed',top:'50%',left:'50%',transform:'translate(-50%,-50%) scale(0.9)',background:'#18181b',color:'#fff',padding:'20px',borderRadius:'12px',zIndex:'10000',display:'none',fontFamily:'Arial,sans-serif',minWidth:'300px',boxShadow:'0 0 15px rgba(0,0,0,0.5)',transition:'transform 0.2s ease, opacity 0.2s ease',opacity:'0',cursor:'default'}); menu.innerHTML=`
Created by: frz
`; document.body.appendChild(menu); document.getElementById('close-menu').addEventListener('click',()=>{ menu.style.opacity='0'; menu.style.transform='translate(-50%,-50%) scale(0.9)'; setTimeout(()=>{menu.style.display='none';},200); }); const slider=document.getElementById('miniplayer-size'); const display=document.getElementById('size-value'); const savedSize=parseFloat(localStorage.getItem(sK))||1; slider.value=savedSize; display.textContent=`${Math.round(savedSize*100)}%`; slider.addEventListener('input',e=>{ const scale=parseFloat(e.target.value); if(miniPlayer)miniPlayer.style.transform=`scale(${scale})`; display.textContent=`${Math.round(scale*100)}%`; localStorage.setItem(sK,scale); }); const dragToggle=document.getElementById('drag-toggle'); const dragSaved=localStorage.getItem(dK); dragToggle.checked=dragSaved===null?true:dragSaved==='true'; dragToggle.addEventListener('change',()=>{localStorage.setItem(dK,dragToggle.checked);}); let isMenuDragging=false,offsetX=0,offsetY=0; const headerDrag=menu.querySelector('#menu-header'); headerDrag.addEventListener('mousedown',e=>{ isMenuDragging=true; const rect=menu.getBoundingClientRect(); offsetX=e.clientX-rect.left; offsetY=e.clientY-rect.top; menu.style.transition='none'; e.preventDefault(); }); document.addEventListener('mousemove',e=>{ if(!isMenuDragging)return; let newX=e.clientX-offsetX; let newY=e.clientY-offsetY; newX=Math.max(0,Math.min(window.innerWidth-menu.offsetWidth,newX)); newY=Math.max(0,Math.min(window.innerHeight-menu.offsetHeight,newY)); menu.style.left=newX+'px'; menu.style.top=newY+'px'; menu.style.transform='translate(0,0)'; }); document.addEventListener('mouseup',()=>{ if(!isMenuDragging)return; isMenuDragging=false; menu.style.transition='transform 0.2s ease, opacity 0.2s ease'; }); } function toggleMenu(){ createMenu(); if(!menu)return; if(menu.style.display==='block'){ menu.style.opacity='0'; menu.style.transform='translate(-50%,-50%) scale(0.9)'; setTimeout(()=>{menu.style.display='none';},200); }else{ menu.style.display='block'; menu.style.opacity='1'; menu.style.transform='translate(-50%,-50%) scale(1)'; } } function checkPlayerState(){ const mini=document.querySelector('.persistent-player__border--mini'); const full=document.querySelector('.persistent-player:not(.persistent-player__border--mini)'); if(full){ document.querySelectorAll('.mini-gear').forEach(e=>e.remove()); if(menu&&menu.style.display==='block'){menu.style.opacity='0';menu.style.transform='translate(-50%,-50%) scale(0.9)';setTimeout(()=>{menu.style.display='none';},200);} }else if(mini){ miniPlayer=mini; if(!mini.querySelector('.mini-gear')){addGearToMiniPlayer();initializeMiniPlayer();} } } const playerObserver=new MutationObserver(()=>{checkPlayerState();}); playerObserver.observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:['class']}); setInterval(checkPlayerState,1000); setTimeout(checkPlayerState,1000); })();