Twitch Latency on Player Controls

Takes the Latency to Broadcaster in the video settings menu and places it in the player controls

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Twitch Latency on Player Controls
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  Takes the Latency to Broadcaster in the video settings menu and places it in the player controls
// @author       tunacan_man
// @match        https://www.twitch.tv/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    
    const LATENCY_UPDATE_INTERVAL = 1000; // 1 seconds
    const REINITIALIZE_INTERVAL = 2000;
    let initialized = false;
    let latencyDiv = null;
    let chatLatencyDiv = null;
    let updateIntervalId = null;

    function waitForElement(selector) {
        return new Promise(resolve => {
            if (document.querySelector(selector)) {
                return resolve(document.querySelector(selector));
            }
            const observer = new MutationObserver(mutations => {
                if (document.querySelector(selector)) {
                    observer.disconnect();
                    resolve(document.querySelector(selector));
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
        });
    }

    async function createLatencyDiv() {
        const controlGroup = await waitForElement(".player-controls__right-control-group");
        if (!controlGroup) return null;
        const existingDiv = document.getElementById('userScript_latencyDiv');
        if (existingDiv) return existingDiv;
        const latencyDiv = document.createElement('div');
        latencyDiv.id = 'userScript_latencyDiv';
        latencyDiv.textContent = '0:00';
        controlGroup.insertAdjacentElement("afterbegin", latencyDiv);
        return latencyDiv;
    }

    async function createChatLatencyDiv() {
        const chatTextArea = await waitForElement(".chat-input__textarea");
        if (!chatTextArea) return null;
        const existingDiv = document.getElementById('userScript_chatLatencyDiv');
        if (existingDiv) return existingDiv;
        const chatLatencyDiv = document.createElement('span');
        chatLatencyDiv.id = 'userScript_chatLatencyDiv';
        chatLatencyDiv.textContent = '0.00';
        chatLatencyDiv.setAttribute("style", "position: absolute;top: -6px;left: 40px;font-size: smaller;color: #eb0400;background-color: #18181b; padding:0px 4px;display: none;");
        chatTextArea.insertAdjacentElement("beforeend", chatLatencyDiv);
        return chatLatencyDiv;
    }

    async function openVideoStats() {
        const settingsButton = await waitForElement("button[data-a-target='player-settings-button']");
        settingsButton.click();
        const advancedButton = await waitForElement("button[data-a-target='player-settings-menu-item-advanced']");
        advancedButton.click();
        const toggleInput = await waitForElement("div[data-a-target='player-settings-submenu-advanced-video-stats']");
        toggleInput.children[0].click();
        const videoStatsDiv = await waitForElement("div[data-a-target='player-overlay-video-stats']");
        videoStatsDiv.style.display = "none";
        settingsButton.click(); // Close the settings menu
    }

    function updateLatency() {
        if (!latencyDiv) return;
        if (!chatLatencyDiv) return;
        const latencyElement = document.querySelector("p[aria-label='Latency To Broadcaster']");
        if (latencyElement) {
            latencyDiv.textContent = latencyElement.textContent;
            let latency = parseFloat(latencyDiv.textContent.slice(0, -5));
            if (latency > 12) {
                // latencyDiv.style.color = "#eb0400";
                chatLatencyDiv.textContent = "⚠️" + latency.toFixed(2);
                chatLatencyDiv.style.display = "inherit";
                chatLatencyDiv.style.border = "1px solid #eb0400";
                chatLatencyDiv.style.color = "#eb0400";
                // document.querySelector("div[data-a-target='chat-input']").style.borderTop = "2px solid #eb0400";
                document.querySelector("div[data-a-target='chat-input']").style.borderRadius = "inherit";
            } else if (latency > 7) {
                // latencyDiv.style.color = "#eb0400";
                chatLatencyDiv.textContent = "⚠️" + latency.toFixed(2);
                chatLatencyDiv.style.display = "inherit";
                // chatLatencyDiv.style.border = "1px solid #eacb00";
                chatLatencyDiv.style.border = "none";
                chatLatencyDiv.style.color = "#eacb00";
                // document.querySelector("div[data-a-target='chat-input']").style.borderTop = "1px solid #eacb00";
                document.querySelector("div[data-a-target='chat-input']").style.borderRadius = "inherit";
            } else {
                latencyDiv.style.color = "#ececec";
                chatLatencyDiv.style.display = "none";
                // document.querySelector("div[data-a-target='chat-input']").style.borderTop = "inherit";
                document.querySelector("div[data-a-target='chat-input']").style.borderRadius = "inherit";
            }
        }
    }

    function startUpdatingLatency() {
        if (updateIntervalId !== null) {
            clearInterval(updateIntervalId);
        }
        updateIntervalId = setInterval(updateLatency, LATENCY_UPDATE_INTERVAL);
    }

    async function init() {
        if (initialized) return;
        latencyDiv = await createLatencyDiv();
        if (!latencyDiv) {
            console.error('LATENCY SCRIPT: Failed to create latency div');
            return;
        }
        chatLatencyDiv = await createChatLatencyDiv();
        if (!chatLatencyDiv) {
            console.error('LATENCY SCRIPT: Failed to create chat latency div');
            return;
        }
        await openVideoStats();
        startUpdatingLatency();
        initialized = true;
        console.log('LATENCY SCRIPT: initialized');
    }

    function reinitialize() {
        if (!document.querySelector(".player-controls__right-control-group") || !document.body.contains(latencyDiv) || !document.querySelector("div[data-a-target='player-overlay-video-stats']") || !document.body.contains(chatLatencyDiv)) {
            initialized = false;
            latencyDiv = null;
            chatLatencyDiv = null;
            if (updateIntervalId !== null) {
                clearInterval(updateIntervalId);
                updateIntervalId = null;
            }
            console.log('LATENCY SCRIPT: Reinitializing script due to missing elements');
            init();
        }
    }

    // Initial setup
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // Periodic reinitialization check
    setInterval(reinitialize, REINITIALIZE_INTERVAL);
})();