您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在Twitch聊天窗口显示总延迟时间,包括缓冲区大小、网络延迟和编码延迟
// ==UserScript== // @name Twitch Latency Display / 延迟显示插件 // @name:en Twitch Latency Display / 延迟显示插件 // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description 在Twitch聊天窗口显示总延迟时间,包括缓冲区大小、网络延迟和编码延迟 // @description:en Display total latency in Twitch chat window, including buffer size, network latency and encoding latency // @author L // @match *://www.twitch.tv/* // @icon https://www.google.com/s2/favicons?sz=64&domain=twitch.tv // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_addStyle // @license MIT // ==/UserScript== /** * 本脚本修改自Chrome扩展 Twitch Latency Display * 原始扩展地址: https://chromewebstore.google.com/detail/twitch-latency-display/gfbfblhgcnaceejlekkogpbjjogdealk * 侵删 */ (function() { 'use strict'; const cssStyles = ` /* 快进按钮样式 */ .FF_buffer_btn { color: var(--color-fill-button-icon, #efeff1); border-radius: var(--border-radius-medium, 0.4rem); width: var(--button-size-default, 3rem); height: var(--button-size-default, 3rem); display: flex; cursor: pointer; border: none; outline: none; background: none; } .FF_buffer_btn > svg { fill: currentcolor; margin: auto; } .FF_buffer_btn:hover { background-color: var(--color-background-button-text-hover, rgba(255, 255, 255, 0.2)); } .FF_buffer_btn:active { background-color: var(--color-background-button-text-active, rgba(255, 255, 255, 0.15)); } /* 显示/隐藏快进按钮 */ :root[hide_ff_btn] .FF_buffer_btn { display: none !important; } /* 隐藏菜单时的样式 */ :root.twitch_latency_hide_menu div[role="dialog"] {pointer-events: none !important; opacity: 0 !important;} /* 视频统计信息样式 - 迷你模式 */ :root:not([hide_video_stats]) .video-player [data-a-target="player-overlay-video-stats"]:not(:hover) > table {box-shadow: unset !important;} :root:not([hide_video_stats]) .video-player [data-a-target="player-overlay-video-stats"]:not(:hover) :is(div, thead, p:not([aria-label="直播者延迟"]):not([aria-label="缓冲区大小"]):not([aria-label="Latency To Broadcaster"]):not([aria-label="Buffer Size"])) {display: none !important;} :root:not([hide_video_stats]) .video-player [data-a-target="player-overlay-video-stats"]:not(:hover) :not(p) {padding: 0 !important;} :root:not([hide_video_stats]) .video-player [data-a-target="player-overlay-video-stats"]:not(:hover) p {margin-right: -16px; padding: 2px 6px; filter: drop-shadow(0px 0px 2px #000) drop-shadow(0px 0px 0px #0009); font-weight: bold;} :root:not([hide_video_stats]) .video-player [data-a-target="player-overlay-video-stats"]:not(:hover) {transform: scale(0.8); transform-origin: 0 0; background: #0004 !important;} /* 当迷你模式关闭时,原始统计信息始终可见 */ :root[hide_video_stats] .video-player [data-a-target="player-overlay-video-stats"] { opacity: 1 !important; display: block !important; } :root[hide_video_stats] .video-player [data-a-target="player-overlay-video-stats"] > table { display: table !important; } /* 弹窗样式 */ .twitch-latency-settings-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.7); z-index: 10000; display: flex; justify-content: center; align-items: center; } .twitch-latency-settings-container { background-color: #18181b; border-radius: 4px; padding: 20px; width: 400px; max-width: 90%; color: #efeff1; font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } .twitch-latency-settings-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .twitch-latency-settings-title { font-size: 18px; font-weight: bold; margin: 0; } .twitch-latency-settings-close { background: none; border: none; color: #efeff1; font-size: 20px; cursor: pointer; padding: 0; } .twitch-latency-settings-option { margin-bottom: 15px; } .twitch-latency-settings-label { display: flex; align-items: center; cursor: pointer; } .twitch-latency-settings-checkbox { margin-right: 10px; } .twitch-latency-settings-number { background-color: #3a3a3d; border: 1px solid #464649; border-radius: 4px; color: #efeff1; padding: 5px 10px; width: 60px; margin-left: 10px; } .twitch-latency-settings-description { margin-top: 5px; color: #adadb8; font-size: 12px; padding-left: 25px; } .twitch-latency-settings-footer { margin-top: 20px; color: #adadb8; font-size: 12px; padding-top: 10px; border-top: 1px solid #464649; } .twitch-latency-settings-select { background-color: #3a3a3d; border: 1px solid #464649; border-radius: 4px; color: #efeff1; padding: 5px 10px; margin-left: 10px; } .twitch-latency-settings-button { padding: 6px 12px; background-color: #772ce8; border: none; border-radius: 4px; color: #fff; cursor: pointer; margin-right: 10px; } `; GM_addStyle(cssStyles); const languages = { 'zh': { // 设置界面 settingsTitle: 'Settings / 设置', showMiniStats: '显示迷你视频统计信息', miniStatsDesc: '在视频左上角显示简化的视频统计信息,包括缓冲区大小和直播者延迟', autoOpenStats: '自动打开视频统计', autoOpenStatsDesc: '进入页面时自动打开视频统计信息(显示迷你统计信息依赖此功能)', showFFBtn: '显示快进直播缓冲按钮', ffBtnDesc: '在聊天输入框旁边显示快进按钮,点击可以将视频跳转到缓冲区的最新位置', showDelayText: '在聊天框显示总延迟', delayTextDesc: '在聊天输入框中显示总延迟时间', encodingLatency: '编码延迟(秒):', encodingLatencyDesc: '在优化系统上使用"低延迟"模式时,OBS 延迟可低至 1-2 秒', language: '语言:', fastForward: '快进直播缓冲', hoverStatsHint: '如果将鼠标悬停在视频的左上角,可以查看完整的高级视频统计信息', // Twitch元素名称 bufferSize: "缓冲区大小", latencyToBroadcaster: "直播者延迟", // 其他文本 totalDelay: '总延迟: {0} 秒', ffBtnTitle: '快进视频缓冲', scriptLoaded: 'Twitch延迟显示插件已加载', openerNotExists: 'opener不存在', openerClosed: 'opener已关闭' }, 'en': { // 设置界面 settingsTitle: 'Settings / 设置', showMiniStats: 'Show Mini Video Stats', miniStatsDesc: 'Display simplified video statistics in the top left corner of the video, including buffer size and latency to broadcaster', autoOpenStats: 'Auto Open Video Stats', autoOpenStatsDesc: 'Automatically open video statistics when entering the page (display mini stats depends on this function)', showFFBtn: 'Show Fast Forward Buffer Button', ffBtnDesc: 'Display a fast forward button next to the chat input, click to jump to the latest point in the buffer', showDelayText: 'Show Total Delay in Chat', delayTextDesc: 'Display the total delay time in the chat input field', encodingLatency: 'Encoding Latency (sec):', encodingLatencyDesc: 'On an optimized system using "Low Latency" mode, OBS delay can be as low as 1-2 seconds', language: 'Language:', fastForward: 'Fast Forward Buffer', hoverStatsHint: 'Hover your mouse over the top left corner of the video to see the full advanced video statistics', // Twitch元素名称 bufferSize: "Buffer Size", latencyToBroadcaster: "Latency To Broadcaster", // 其他文本 totalDelay: 'Total Delay: {0} sec', ffBtnTitle: 'Fast Forward Video Buffer', scriptLoaded: 'Twitch Latency Display loaded', openerNotExists: 'opener does not exist', openerClosed: 'opener closed' } }; // 默认设置 const default_options = { sw_show_small_video_stats_css: true, sw_show_ff_btn: true, sw_show_delay_text: true, sw_auto_open_stats: true, encoding_latency: 1.5, language: getBrowserLanguage() }; // 获取浏览器语言 function getBrowserLanguage() { const lang = navigator.language.toLowerCase(); return lang.startsWith('zh') ? 'zh' : 'en'; } // 获取当前语言 function getCurrentLanguage() { return GM_getValue('language', default_options.language); } // 获取翻译文本 function getText(key, ...args) { const currentLang = getCurrentLanguage(); let text = languages[currentLang][key] || languages['en'][key] || key; // 如果有参数,替换占位符 if (args.length > 0) { for (let i = 0; i < args.length; i++) { text = text.replace(`{${i}}`, args[i]); } } return text; } // 初始化设置 function initSettings() { for (const key in default_options) { if(GM_getValue(key) === undefined) { GM_setValue(key, default_options[key]); console.log(key, "设置为默认值"); } } } // 创建设置弹窗 function createSettingsUI() { // 移除现有弹窗(如果存在) const existingOverlay = document.querySelector('.twitch-latency-settings-overlay'); if (existingOverlay) { existingOverlay.remove(); return; } // 创建弹窗 const overlay = document.createElement('div'); overlay.className = 'twitch-latency-settings-overlay'; const container = document.createElement('div'); container.className = 'twitch-latency-settings-container'; // 弹窗头部 const header = document.createElement('div'); header.className = 'twitch-latency-settings-header'; const title = document.createElement('h2'); title.className = 'twitch-latency-settings-title'; title.textContent = getText('settingsTitle'); const closeBtn = document.createElement('button'); closeBtn.className = 'twitch-latency-settings-close'; closeBtn.textContent = '×'; closeBtn.addEventListener('click', () => overlay.remove()); header.appendChild(title); header.appendChild(closeBtn); // 设置选项 const optionsContainer = document.createElement('div'); // 语言选择 const langOption = document.createElement('div'); langOption.className = 'twitch-latency-settings-option'; const langLabel = document.createElement('label'); langLabel.className = 'twitch-latency-settings-label'; langLabel.appendChild(document.createTextNode(getText('language'))); const langSelect = document.createElement('select'); langSelect.className = 'twitch-latency-settings-select'; const zhOption = document.createElement('option'); zhOption.value = 'zh'; zhOption.textContent = '中文'; zhOption.selected = getCurrentLanguage() === 'zh'; const enOption = document.createElement('option'); enOption.value = 'en'; enOption.textContent = 'English'; enOption.selected = getCurrentLanguage() === 'en'; langSelect.appendChild(zhOption); langSelect.appendChild(enOption); langSelect.addEventListener('change', (e) => { GM_setValue('language', e.target.value); // 更新界面语言 overlay.remove(); setTimeout(createSettingsUI, 100); // 重新打开设置以更新语言 }); langLabel.appendChild(langSelect); langOption.appendChild(langLabel); // 小视频统计信息选项 const statsOption = document.createElement('div'); statsOption.className = 'twitch-latency-settings-option'; const statsLabel = document.createElement('label'); statsLabel.className = 'twitch-latency-settings-label'; const statsCheckbox = document.createElement('input'); statsCheckbox.className = 'twitch-latency-settings-checkbox'; statsCheckbox.type = 'checkbox'; statsCheckbox.checked = GM_getValue('sw_show_small_video_stats_css', true); statsCheckbox.addEventListener('change', (e) => { GM_setValue('sw_show_small_video_stats_css', e.target.checked); toggleSmallVideoStats(e.target.checked); }); statsLabel.appendChild(statsCheckbox); statsLabel.appendChild(document.createTextNode(getText('showMiniStats'))); const statsDesc = document.createElement('div'); statsDesc.className = 'twitch-latency-settings-description'; statsDesc.textContent = getText('miniStatsDesc'); statsOption.appendChild(statsLabel); statsOption.appendChild(statsDesc); // 自动打开视频统计选项 const autoOpenStatsOption = document.createElement('div'); autoOpenStatsOption.className = 'twitch-latency-settings-option'; const autoOpenStatsLabel = document.createElement('label'); autoOpenStatsLabel.className = 'twitch-latency-settings-label'; const autoOpenStatsCheckbox = document.createElement('input'); autoOpenStatsCheckbox.className = 'twitch-latency-settings-checkbox'; autoOpenStatsCheckbox.type = 'checkbox'; autoOpenStatsCheckbox.checked = GM_getValue('sw_auto_open_stats', true); autoOpenStatsCheckbox.addEventListener('change', (e) => { GM_setValue('sw_auto_open_stats', e.target.checked); }); autoOpenStatsLabel.appendChild(autoOpenStatsCheckbox); autoOpenStatsLabel.appendChild(document.createTextNode(getText('autoOpenStats'))); const autoOpenStatsDesc = document.createElement('div'); autoOpenStatsDesc.className = 'twitch-latency-settings-description'; autoOpenStatsDesc.textContent = getText('autoOpenStatsDesc'); autoOpenStatsOption.appendChild(autoOpenStatsLabel); autoOpenStatsOption.appendChild(autoOpenStatsDesc); // 快进按钮选项 const ffBtnOption = document.createElement('div'); ffBtnOption.className = 'twitch-latency-settings-option'; const ffBtnLabel = document.createElement('label'); ffBtnLabel.className = 'twitch-latency-settings-label'; const ffBtnCheckbox = document.createElement('input'); ffBtnCheckbox.className = 'twitch-latency-settings-checkbox'; ffBtnCheckbox.type = 'checkbox'; ffBtnCheckbox.checked = GM_getValue('sw_show_ff_btn', true); ffBtnCheckbox.addEventListener('change', (e) => { GM_setValue('sw_show_ff_btn', e.target.checked); toggleFFBtn(e.target.checked); }); ffBtnLabel.appendChild(ffBtnCheckbox); ffBtnLabel.appendChild(document.createTextNode(getText('showFFBtn'))); const ffBtnDesc = document.createElement('div'); ffBtnDesc.className = 'twitch-latency-settings-description'; ffBtnDesc.textContent = getText('ffBtnDesc'); ffBtnOption.appendChild(ffBtnLabel); ffBtnOption.appendChild(ffBtnDesc); // 聊天框显示总延迟选项 const delayTextOption = document.createElement('div'); delayTextOption.className = 'twitch-latency-settings-option'; const delayTextLabel = document.createElement('label'); delayTextLabel.className = 'twitch-latency-settings-label'; const delayTextCheckbox = document.createElement('input'); delayTextCheckbox.className = 'twitch-latency-settings-checkbox'; delayTextCheckbox.type = 'checkbox'; delayTextCheckbox.checked = GM_getValue('sw_show_delay_text', true); delayTextCheckbox.addEventListener('change', (e) => { GM_setValue('sw_show_delay_text', e.target.checked); // 立即应用设置 if (!e.target.checked) { // 如果关闭了显示,清除当前显示的文本 const output1 = document.querySelector(".chat-wysiwyg-input__placeholder"); const output2 = document.querySelector(`[data-a-target="chat-input"]`); if (output1) output1.textContent = ''; if (output2) output2.removeAttribute("placeholder"); } }); delayTextLabel.appendChild(delayTextCheckbox); delayTextLabel.appendChild(document.createTextNode(getText('showDelayText'))); const delayTextDesc = document.createElement('div'); delayTextDesc.className = 'twitch-latency-settings-description'; delayTextDesc.textContent = getText('delayTextDesc'); delayTextOption.appendChild(delayTextLabel); delayTextOption.appendChild(delayTextDesc); // 编码延迟设置 const encodingOption = document.createElement('div'); encodingOption.className = 'twitch-latency-settings-option'; const encodingLabel = document.createElement('label'); encodingLabel.className = 'twitch-latency-settings-label'; encodingLabel.appendChild(document.createTextNode(getText('encodingLatency'))); const encodingInput = document.createElement('input'); encodingInput.className = 'twitch-latency-settings-number'; encodingInput.type = 'number'; encodingInput.min = '0'; encodingInput.step = '0.1'; encodingInput.value = GM_getValue('encoding_latency', 1.5); encodingInput.addEventListener('change', (e) => { const value = parseFloat(e.target.value); if (!isNaN(value) && value >= 0) { GM_setValue('encoding_latency', value); setEncodingLatency(value); } }); encodingLabel.appendChild(encodingInput); const encodingDesc = document.createElement('div'); encodingDesc.className = 'twitch-latency-settings-description'; encodingDesc.textContent = getText('encodingLatencyDesc'); encodingOption.appendChild(encodingLabel); encodingOption.appendChild(encodingDesc); // 快速操作按钮 const fastActionOption = document.createElement('div'); fastActionOption.className = 'twitch-latency-settings-option'; const fastForwardBtn = document.createElement('button'); fastForwardBtn.textContent = getText('fastForward'); fastForwardBtn.className = 'twitch-latency-settings-button'; fastForwardBtn.addEventListener('click', () => { fastForwardBuffer(); fastForwardBtn.blur(); }); fastActionOption.appendChild(fastForwardBtn); // 添加页脚说明 const footer = document.createElement('div'); footer.className = 'twitch-latency-settings-footer'; footer.textContent = getText('hoverStatsHint'); // 组装弹窗 optionsContainer.appendChild(langOption); optionsContainer.appendChild(statsOption); optionsContainer.appendChild(autoOpenStatsOption); optionsContainer.appendChild(ffBtnOption); optionsContainer.appendChild(delayTextOption); optionsContainer.appendChild(encodingOption); optionsContainer.appendChild(fastActionOption); container.appendChild(header); container.appendChild(optionsContainer); container.appendChild(footer); overlay.appendChild(container); document.body.appendChild(overlay); // 点击弹窗外部关闭弹窗 overlay.addEventListener('click', (e) => { if (e.target === overlay) { overlay.remove(); } }); } // 注册(不可用)单个菜单命令 function registerMenuCommand() { GM_registerMenuCommand("Settings / 设置", createSettingsUI); } // 编码延迟的全局变量 let encoding_latency_localvalue; // 主监听函数 function main_listener(interval = 900) { let last_timeStamp = 0; return async function(event) { if(event.timeStamp - last_timeStamp <= interval) return; // 节流 last_timeStamp = event.timeStamp; if(event.target.nodeName !== "VIDEO") { return; } if(typeof encoding_latency_localvalue === "undefined") { encoding_latency_localvalue = GM_getValue("encoding_latency", 1.5); } const output1 = document.querySelector(".chat-wysiwyg-input__placeholder"); // ".rich-input-container" 的第一个子元素 const output2 = document.querySelector(`[data-a-target="chat-input"]`); // (`textarea[aria-label="发送消息"]`); const chat_left = document.querySelector(`[data-test-selector="chat-room-component-layout"]`)?.getBoundingClientRect().left; if((output1 || output2) && chat_left && window.innerWidth > chat_left) { // 获取当前语言的延迟文本标签 const currentLang = getCurrentLanguage(); const bufferSizeLabel = languages[currentLang].bufferSize; const latencyLabel = languages[currentLang].latencyToBroadcaster; const delay1 = document.querySelector(`[aria-label="${bufferSizeLabel}"]`)?.textContent?.match(/([0-9.]+)/)?.[1] || document.querySelector(`[aria-label="Buffer Size"]`)?.textContent?.match(/([0-9.]+)/)?.[1] || document.querySelector(`[aria-label="缓冲区大小"]`)?.textContent?.match(/([0-9.]+)/)?.[1]; // Buffer Size const delay2 = document.querySelector(`[aria-label="${latencyLabel}"]`)?.textContent?.match(/([0-9.]+)/)?.[1] || document.querySelector(`[aria-label="Latency To Broadcaster"]`)?.textContent?.match(/([0-9.]+)/)?.[1] || document.querySelector(`[aria-label="直播者延迟"]`)?.textContent?.match(/([0-9.]+)/)?.[1]; // Latency To Broadcaster if(delay1 && delay2) { const max_delay = Math.max(delay1 * 1, delay2 * 1); // 回放以人为增加缓冲区时,广播延迟的缓冲区值更新较慢,暂时使用较大值 // 检查是否启用聊天框显示总延迟 if (GM_getValue('sw_show_delay_text', true)) { const text = getText('totalDelay', (max_delay + encoding_latency_localvalue).toFixed(2)); output1 && (output1.textContent = text); output2?.setAttribute("placeholder", text); } } else { // 检查是否自动打开视频统计 if(GM_getValue("sw_auto_open_stats", true)) { showVideoStats.menuClick(); } } createFFButton(); } }; } // 弹出聊天窗口的计时器 function popupChat_intervalTimer() { if(!opener?.document.documentElement) { console.log(getText('openerNotExists')); return; } const loop_timer = setInterval(async () => { if(!opener?.document.documentElement) { console.log(getText('openerClosed')); clearInterval(loop_timer); return; } if(!encoding_latency_localvalue) { encoding_latency_localvalue = GM_getValue("encoding_latency", 1.5); } const output1 = document.querySelector(".chat-wysiwyg-input__placeholder"); const output2 = document.querySelector(`[data-a-target="chat-input"]`); if(output1 || output2) { // 获取当前语言的延迟文本标签 const currentLang = getCurrentLanguage(); const bufferSizeLabel = languages[currentLang].bufferSize; const latencyLabel = languages[currentLang].latencyToBroadcaster; const delay1 = opener.document.querySelector(`[aria-label="${bufferSizeLabel}"]`)?.textContent?.match(/([0-9.]+)/)?.[1] || opener.document.querySelector(`[aria-label="Buffer Size"]`)?.textContent?.match(/([0-9.]+)/)?.[1] || opener.document.querySelector(`[aria-label="缓冲区大小"]`)?.textContent?.match(/([0-9.]+)/)?.[1]; // Buffer Size const delay2 = opener.document.querySelector(`[aria-label="${latencyLabel}"]`)?.textContent?.match(/([0-9.]+)/)?.[1] || opener.document.querySelector(`[aria-label="Latency To Broadcaster"]`)?.textContent?.match(/([0-9.]+)/)?.[1] || opener.document.querySelector(`[aria-label="直播者延迟"]`)?.textContent?.match(/([0-9.]+)/)?.[1]; // Latency To Broadcaster if(delay1 && delay2) { const max_delay = Math.max(delay1 * 1, delay2 * 1); // 检查是否启用聊天框显示总延迟 if (GM_getValue('sw_show_delay_text', true)) { const text = getText('totalDelay', (max_delay + encoding_latency_localvalue).toFixed(2)); output1 && (output1.textContent = text); output2?.setAttribute("placeholder", text); } } else { // 检查是否自动打开视频统计 if(GM_getValue("sw_auto_open_stats", true)) { showVideoStats.menuClick(); } } createFFButton(true); } }, 1000); } // 显示视频统计信息 const showVideoStats = (function() { let run_flag; let hidemenu_css_timer; function menuClick(delay = 200) { if(run_flag || !document.querySelector(`[data-a-target="player-settings-button"]`)) return; // 设置按钮 run_flag = true; let limit = 20; const menuloop = setInterval(() => { const visibled_video_stats = !!document.querySelector(`[data-a-target="player-overlay-video-stats"]`); if(limit-- <= 0) { console.error(menuloop, "menu limit!"); clearInterval(menuloop); run_flag = false; return; } if(!visibled_video_stats) { const visibled_menu = !!document.querySelector(`[data-a-target="player-settings-menu"]`); const setting_btn = document.querySelectorAll(`[data-a-target="player-settings-button"]`); const advanced_btn = document.querySelectorAll(`[data-a-target="player-settings-menu-item-advanced"]`); const video_stats = document.querySelector(`[data-a-target="player-settings-submenu-advanced-video-stats"] input`); if(!visibled_menu && setting_btn.length) { // 设置 // 隐藏菜单激活过程 hidemenu_css_timer && clearTimeout(hidemenu_css_timer); document.documentElement.classList.add("twitch_latency_hide_menu"); hidemenu_css_timer = setTimeout(() => { document.documentElement.classList.remove("twitch_latency_hide_menu"); hidemenu_css_timer = null; }, 2000); setting_btn.forEach(e => e.click()); return; } else if(advanced_btn.length) { // 高级菜单 advanced_btn.forEach(e => e.click()); return; } if(video_stats?.checked === false) { // 视频统计 video_stats.click(); return; } } else { // 从2023年1月底开始按钮被多次捕获并需要检查按钮可见性 const main_btn = document.querySelectorAll(`[data-test-selector="main-menu"]`); const close_btn = document.querySelectorAll(`[data-a-target="player-settings-menu"] button:not([data-a-target]):has(svg)`); let click_flag; if(main_btn.length) { // 回到主菜单 // 属性生成较慢 main_btn.forEach(e => { if(e.getBoundingClientRect()?.width) { click_flag = true; e.click(); } }); if(click_flag) return; } if(close_btn.length) { // 关闭 // 属性生成较慢 close_btn.forEach(e => { if(e.getBoundingClientRect()?.width) { e.click(); click_flag = true; } }); if(click_flag) return; } { // 设置菜单关闭确认 clearInterval(menuloop); run_flag = false; // 提前结束菜单隐藏计时器 if(hidemenu_css_timer) { clearTimeout(hidemenu_css_timer); document.documentElement.classList.remove("twitch_latency_hide_menu"); hidemenu_css_timer = null; } return; } } }, delay); } return { menuClick }; })(); // 创建快进按钮(插入到聊天输入区域) function createFFButton(is_popup = false) { if(document.querySelector(".FF_buffer_btn") || !GM_getValue("sw_show_ff_btn", true)) return; const container = document.querySelector(`[data-test-selector="chat-input-buttons-container"] > :last-child`); if (!container) return; const ff_btn = document.createElement("button"); ff_btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"> <path d="M11,10.036L2.413,16.888V3.183Z"/> <path d="M18.623,10.036l-8.587,6.852V3.183Z"/> </svg>`; ff_btn.setAttribute("class", "FF_buffer_btn"); ff_btn.setAttribute("title", getText('ffBtnTitle')); if(is_popup) { ff_btn.addEventListener("click", function() { opener?.document.querySelectorAll("video").forEach(video => { video.buffered.length && (video.currentTime = video.buffered.end(video.buffered.length - 1)); }); this.blur(); }); } else { ff_btn.addEventListener("click", function() { fastForwardBuffer(); this.blur(); }); } container.prepend(ff_btn); } // 切换小型视频统计显示 function toggleSmallVideoStats(show) { if(show) { document.documentElement.removeAttribute("hide_video_stats"); // 自动打开视频统计 showVideoStats.menuClick(); } else { document.documentElement.setAttribute("hide_video_stats", ""); // 确保视频统计信息已打开 const videoStats = document.querySelector(`[data-a-target="player-overlay-video-stats"]`); if (!videoStats) { // 如果统计信息未显示,则打开它 showVideoStats.menuClick(); } } } // 切换快进按钮显示 function toggleFFBtn(show) { if(show) { document.documentElement.removeAttribute("hide_ff_btn"); createFFButton(); } else { document.documentElement.setAttribute("hide_ff_btn", ""); // 移除现有按钮 (虽然CSS已经隐藏,但为了保持DOM干净) const existingBtn = document.querySelector(".FF_buffer_btn"); if(existingBtn) existingBtn.remove(); } } // 设置编码延迟 function setEncodingLatency(value) { encoding_latency_localvalue = undefined; } // 快进缓冲区 function fastForwardBuffer() { document.querySelectorAll("video").forEach(video => { video.buffered.length && (video.currentTime = video.buffered.end(video.buffered.length - 1)); }); } // 初始化脚本 function initScript() { initSettings(); registerMenuCommand(); const sw_show_ff_btn = GM_getValue("sw_show_ff_btn", true); if(!sw_show_ff_btn) { document.documentElement.setAttribute("hide_ff_btn", ""); } const pathname_split = location.pathname?.split("/"); if(pathname_split[1] === "popout" && pathname_split[3] === "chat") { popupChat_intervalTimer(); return; } const show_stats = GM_getValue("sw_show_small_video_stats_css", true); if(!show_stats) { document.documentElement.setAttribute("hide_video_stats", ""); } // 检查是否自动打开视频统计 if(GM_getValue("sw_auto_open_stats", true)) { // 延迟一点时间确保页面元素已加载 setTimeout(() => { const videoStats = document.querySelector(`[data-a-target="player-overlay-video-stats"]`); if (!videoStats) { showVideoStats.menuClick(); } }, 2000); } if(!document.documentElement.hasAttribute("video_latency_display")) { document.documentElement.setAttribute("video_latency_display", ""); document.addEventListener("timeupdate", main_listener(900), true); } } // 启动脚本 initScript(); console.log(getText('scriptLoaded')); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址