Killing Kick Latency

kick 레이턴시 줄이기

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Killing Kick Latency
// @namespace    http://tampermonkey.net/
// @version      1.01
// @description  kick 레이턴시 줄이기
// @author       purirun
// @match        https://www.kick.com/*
// @match        https://kick.com/*
// @grant        none
// @run-at       document-idle
// @license MIT
// ==/UserScript==

(function () {
  const VIDEO_SEL = 'video';
  let delayText = null;
  let isCooling = false;
  let chaseInterval = null;

  function getControlBar() {
    const all = document.querySelectorAll('.z-controls');
    // absolute bottom-0 인 컨트롤바
    return [...all].find(el => el.classList.contains('bottom-0'));
  }

  function createSyncBtn() {
    if (document.getElementById('kick-tiny-sync')) return;

    const bar = getControlBar();
    if (!bar) return;

    // index 1 = 오른쪽 버튼 그룹
    const rightGroup = bar.children[1];
    if (!rightGroup) return;

    const syncBtn = document.createElement('div');
    syncBtn.id = 'kick-tiny-sync';
    syncBtn.title = '클릭하면 딜레이가 즉시 동기화됩니다.';
    syncBtn.style.cssText = [
      'display:inline-flex',
      'align-items:center',
      'gap:5px',
      'background:rgba(255,255,255,0.1)',
      'border:1px solid transparent',
      'border-radius:6px',
      'padding:3px 8px',
      'cursor:pointer',
      'user-select:none',
      'transition:all 0.2s',
      'font-family:monospace',
      'height:32px',
      'box-sizing:border-box',
      'flex-shrink:0',
    ].join(';');

    syncBtn.onmouseover = () => {
      syncBtn.style.background = 'rgba(255,255,255,0.2)';
      syncBtn.style.border     = '1px solid #53FC18';
    };
    syncBtn.onmouseout = () => {
      if (!isCooling) {
        syncBtn.style.background = 'rgba(255,255,255,0.1)';
        syncBtn.style.border     = '1px solid transparent';
      }
    };

    const icon = document.createElement('span');
    icon.innerText      = '⚡';
    icon.style.fontSize = '12px';

    delayText = document.createElement('span');
    delayText.innerText     = '-.-s';
    delayText.style.cssText = 'font-size:12px;font-weight:bold;color:#fff;line-height:1;white-space:nowrap;';

    syncBtn.appendChild(icon);
    syncBtn.appendChild(delayText);

    syncBtn.onclick = (e) => {
      e.preventDefault();
      e.stopPropagation();
      if (isCooling) return;

      const vid = document.querySelector(VIDEO_SEL);
      if (!vid) return;

      // 버퍼 끝으로 점프
      if (vid.buffered.length > 0) {
        vid.currentTime = vid.buffered.end(vid.buffered.length - 1) - 0.3;
      } else {
        vid.currentTime = vid.duration;
      }

      // 딜레이 남아있으면 1.3배속으로 추격
      if (chaseInterval) clearInterval(chaseInterval);
      chaseInterval = setInterval(() => {
        const v = document.querySelector(VIDEO_SEL);
        if (!v) { clearInterval(chaseInterval); return; }
        const delay = v.buffered.length > 0
          ? v.buffered.end(v.buffered.length - 1) - v.currentTime
          : 0;
        if (delay > 1) {
          v.playbackRate = 1.3;
        } else {
          v.playbackRate = 1.0;
          clearInterval(chaseInterval);
        }
      }, 200);

      isCooling = true;
      delayText.innerText   = 'OK!';
      delayText.style.color = '#53FC18';
      syncBtn.style.border  = '1px solid #53FC18';

      setTimeout(() => {
        isCooling = false;
        syncBtn.style.border     = '1px solid transparent';
        syncBtn.style.background = 'rgba(255,255,255,0.1)';
      }, 1500);
    };

    // 오른쪽 그룹의 첫 번째 버튼 앞에 삽입
    const firstBtn = rightGroup.querySelector('button');
    if (firstBtn) {
      rightGroup.insertBefore(syncBtn, firstBtn);
    } else {
      rightGroup.appendChild(syncBtn);
    }
  }

  // 딜레이 수치 갱신
  setInterval(() => {
    if (!document.getElementById('kick-tiny-sync')) {
      delayText = null;
      createSyncBtn();
      return;
    }
    if (!delayText || isCooling) return;

    const vid = document.querySelector(VIDEO_SEL);
    if (!vid || vid.paused) {
      delayText.innerText   = '---';
      delayText.style.color = '#888';
      return;
    }

    if (vid.buffered.length > 0) {
      const delay = Math.max(0, vid.buffered.end(vid.buffered.length - 1) - vid.currentTime);
      delayText.innerText   = delay.toFixed(1) + 's';
      delayText.style.color = delay < 1.5 ? '#53FC18' : delay < 3 ? '#FFD700' : '#FF4D4D';
    } else {
      delayText.innerText   = '???';
      delayText.style.color = '#888';
    }
  }, 500);

  // 초기 생성 - 컨트롤바는 hover시에만 나타나므로 계속 재시도
  let attempts = 0;
  const initInterval = setInterval(() => {
    if (document.body) {
      createSyncBtn();
      if (document.getElementById('kick-tiny-sync')) clearInterval(initInterval);
    }
    if (++attempts >= 40) clearInterval(initInterval);
  }, 500);
})();