Kick Stream Delay Display

Displays real-time stream latency next to viewer count on Kick.com with auto speed adjustment and Mo'Kick compatibility

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Advertisement:

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

Advertisement:

// ==UserScript==
// @name         Kick Stream Delay Display
// @namespace    http://tampermonkey.net/
// @version      1.2.1
// @description  Displays real-time stream latency next to viewer count on Kick.com with auto speed adjustment and Mo'Kick compatibility
// @description:pl Wyświetla opóźnienie streamu obok licznika widzów na Kick.com z auto-przyspieszaniem i zgodnością z Mo'Kick
// @author       Premiumsmart
// @match        https://kick.com/*
// @icon         https://kick.com/favicon.ico
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // Opcjonalnie, zmień te wartości według własnych potrzeb
    const targetDelay = 6;        // Opóźnienie w sekundach, przy którym zmieniamy prędkość
    const speedUpFactor = 1.25;   // Współczynnik przyspieszenia

    // Kick zmienił swój frontend i USUNĄŁ atrybut data-testid="viewer-count".
    // Dlatego licznik widzów trzeba znaleźć strukturalnie. Rząd statystyk to:
    //   div.flex.items-center.gap-2.self-end  (rodzic)
    //     └ div.flex.items-center.gap-1  (komórka: ikona + liczba)   ← "Viewers"
    // Mo'Kick DODAJE swoje komórki (Chatters / Participation) do TEGO SAMEGO rzędu,
    // więc wstawienie badge jako pierwszej komórki działa identycznie z Mo'Kick i bez.

    function getVideo() {
        return document.querySelector('#video-player') || document.querySelector('video');
    }

    // Zwraca { row } — rząd statystyk, do którego wstawiamy badge (jako pierwsze dziecko).
    function findStatsRow() {
        // 0) Zgodność wstecz: jeśli Kick kiedyś przywróci testid.
        const legacy = document.querySelector('[data-testid="viewer-count"]');
        if (legacy) return legacy;

        // A) Mo'Kick aktywny: pierwszy animowany licznik = widzowie (zielony text-primary-base).
        //    struktura: span.moKick-number-counter → div.text-*-base → div.komórka(svg) → div.rząd(self-end)
        const moCounter = document.querySelector('.moKick-number-counter');
        if (moCounter) {
            const cell = moCounter.closest('div.flex.items-center.gap-1')
                || (moCounter.parentElement && moCounter.parentElement.parentElement);
            if (cell && cell.parentElement) return cell.parentElement;
        }

        // B) Natywnie (bez Mo'Kick): rząd wyrównany do prawej (self-end) z ikoną i liczbą.
        const scope = document.querySelector('#channel-content') || document;
        const selfEndRows = scope.querySelectorAll('div[class*="self-end"]');
        for (const row of selfEndRows) {
            if (row.querySelector('svg') && /\d/.test(row.textContent)) return row;
        }

        // C) Ostateczny fallback: prawa kolumna metadanych obok tytułu streamu.
        const title = document.querySelector('[data-testid="livestream-title"]');
        if (title) {
            let anc = title;
            for (let i = 0; i < 6 && anc.parentElement; i++) anc = anc.parentElement;
            const col = anc.querySelector('div[class*="items-end"]');
            if (col) return col;
        }

        return null;
    }

    function createBadge(delayText) {
        const badge = document.createElement('div');
        badge.id = 'delay-display';
        badge.className = 'flex items-center gap-1 text-sm font-bold relative';
        // Style inline — nie polegamy na tym, że klasy Tailwind Kicka istnieją.
        badge.style.display = 'flex';
        badge.style.alignItems = 'center';
        badge.style.gap = '4px';
        badge.style.color = '#53FC18'; // Zielony kolor Kick
        badge.style.fontWeight = 'bold';
        badge.style.fontSize = '0.875rem';
        badge.innerHTML = `
            <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
                <path d="M8 1L12 6H10V12H6V6H4L8 1Z"></path>
            </svg>
            <span id="delay-text">${delayText}</span>
        `;
        return badge;
    }

    function updateDelayDisplay(delayText) {
        const row = findStatsRow();
        if (!row) return; // nie ma jeszcze paska statystyk — spróbujemy w następnym ticku

        let badge = document.getElementById('delay-display');
        if (!badge) {
            badge = createBadge(delayText);
        }

        // Self-heal: zawsze upewnij się, że badge jest PIERWSZĄ komórką aktualnego rzędu.
        // Kick (React) i Mo'Kick potrafią przebudować DOM i usunąć/przenieść nasz element.
        if (badge.parentElement !== row || row.firstElementChild !== badge) {
            row.insertBefore(badge, row.firstChild);
        }

        const textSpan = badge.querySelector('#delay-text');
        if (textSpan) textSpan.textContent = delayText;
    }

    // Jeden tick: policz opóźnienie, dostosuj prędkość, spróbuj wyświetlić.
    // Każdy krok w try/catch — jeden wyjątek NIE może zabić całej pętli (błąd oryginału v1.1).
    function tick() {
        let delayText = '';
        try {
            const video = getVideo();
            if (video && video.buffered && video.buffered.length > 0) {
                const currentTime = video.currentTime;
                const bufferEnd = video.buffered.end(video.buffered.length - 1);
                const delay = bufferEnd - currentTime;

                if (delay > targetDelay) {
                    video.playbackRate = speedUpFactor;
                } else {
                    video.playbackRate = 1.0; // Przywróć normalną prędkość
                }

                delayText = `${delay.toFixed(2)}s`;
            }
        } catch (e) {
            // ignoruj — nie przerywaj pętli
        }

        try {
            if (delayText) updateDelayDisplay(delayText);
        } catch (e) {
            // ignoruj — nie przerywaj pętli
        }
    }

    // Poczekaj aż pojawi się wideo, potem uruchom stały interwał.
    // setInterval (a nie łańcuch setTimeout) gwarantuje, że pętla żyje mimo błędów.
    function start() {
        console.log('Kick Stream Delay Display v1.2: Started');
        tick();
        setInterval(tick, 1000);
    }

    function waitForVideo() {
        if (getVideo()) {
            start();
        } else {
            setTimeout(waitForVideo, 500);
        }
    }

    waitForVideo();
})();