文章详情

返回首页

用AI搓了两个youtube 详细统计信息 精简版

分享文章 作者: Ws01 创建时间: 2026-01-18 📝 字数: 12,241 字 👁️ 阅读: 13 次

用AI搓了两个youtube 详细统计信息 精简版
转自:NS论坛
在chrome和firefox浏览器测过,其他未知...
使用方法:安装Tampermonkey(篡改猴),添加新脚本,复制并粘贴脚本后按Ctrl+s保存,打开详细统计信息。

1、迷你版

// ==UserScript==

// @name YouTube Stats for Mini 迷你版v2
// @namespace http://tampermonkey.net/
// @version 1.0.2
// @description YouTube Stats for Mini 迷你版v2
// @author jian
// @match ://www.youtube.com/
// @match ://m.youtube.com/
// @grant GM_addStyle
// @run-at document-start
// ==/UserScript==

(function () {
'use strict';

/ ================= 样式优化:改为 absolute 定位 ================= /
GM_addStyle(`
.html5-video-info-panel.ytp-sfn {
/ 关键修改:改为绝对定位,使其相对于播放器容器滚动 /
position: absolute !important;
top: 10px !important;
left: 10px !important;
z-index: 99999 !important;
opacity: 0.85 !important;
font-size: 11px !important;
width: 410px !important;
max-width: 410px !important;
background: rgba(0, 0, 0, 0.7) !important;
border-radius: 4px;
overflow-x: hidden !important;
overflow-y: auto;
}

.html5-video-info-panel.ytp-sfn
.html5-video-info-panel-content > div {
white-space: normal !important;
}

.yt-mbps {
margin-left: auto;
margin-right: 24px;
padding-left: 8px;
white-space: nowrap;
color: #ff8c00; / 淡橙色 /
font-weight: bold;
}

.html5-video-info-panel.ytp-sfn
.ytp-horizonchart {
width: 190px !important;
max-width: 190px !important;
}

.html5-video-info-panel.ytp-sfn
.ytp-horizonchart canvas {
width: 190px !important;
}
`);

const ALLOWED = [
'Viewport / Frames',
'Current / Optimal Res',
'Connection Speed',
'Network Activity',
'Buffer Health'
];

function cleanRows(panel) {
const rows = (panel || document).querySelectorAll('.html5-video-info-panel-content > div');
rows.forEach(row => {
const label = row.querySelector(':scope > div:first-child');
const title = label ? label.textContent.trim() : '';
if (title === 'Color' || title === '' || !ALLOWED.includes(title)) {
row.style.display = 'none';
} else {
row.style.display = 'flex'; // 确保 flex 布局以对齐 MB/s
}
});
}

function cleanRowsRepeated(panel, times = 5, intervalMs = 200) {
let count = 0;
const timer = setInterval(() => {
cleanRows(panel);
count++;
if (count >= times) clearInterval(timer);
}, intervalMs);
}

function setupSpeed(panel) {
const rows = panel.querySelectorAll('.html5-video-info-panel-content > div');
let kbpsSpan = null;
let resRow = null;

for (const row of rows) {
const label = row.querySelector(':scope > div:first-child');
if (!label) continue;
const title = label.textContent.trim();
if (title === 'Connection Speed') {
kbpsSpan = row.querySelector('span span:last-child, span:last-child');
}
if (title === 'Current / Optimal Res') {
resRow = row;
}
}

if (!kbpsSpan || !resRow) return;

let mbpsSpan = resRow.querySelector('.yt-mbps');
if (!mbpsSpan) {
mbpsSpan = document.createElement('span');
mbpsSpan.className = 'yt-mbps';
resRow.appendChild(mbpsSpan);
}

const update = () => {
const text = kbpsSpan.textContent;
const match = text.match(/([\d.]+)\s*Kbps/i);
if (!match) return;
const kbps = parseFloat(match[1]);
const mbps = (kbps / 8 / 1024).toFixed(2);
mbpsSpan.textContent = ${mbps} MB/s;
};

update();
const observer = new MutationObserver(update);
observer.observe(kbpsSpan, { characterData: true, childList: true, subtree: true });
panel._speedObserver = observer;
}

/ ================= 核心修改:确保面板挂载在播放器内部 ================= /
function setupPanelObserver() {
const observer = new MutationObserver(mutations => {
for (const m of mutations) {
for (const node of m.addedNodes) {
if (node.nodeType !== 1) continue;

const panel = node.classList?.contains('html5-video-info-panel')
? node
: node.querySelector?.('.html5-video-info-panel');

if (panel) {
// 强制将面板移动到播放器主容器内,这样它就会随视频滚动
const player = document.querySelector('#movie_player') || document.querySelector('.html5-video-player');
if (player && panel.parentElement !== player) {
player.appendChild(panel);
}

setTimeout(() => {
cleanRows(panel);
setupSpeed(panel);
cleanRowsRepeated(panel);
}, 100);
}
}
}
});

observer.observe(document.body, { childList: true, subtree: true });
}

function setupVideoListener() {
const video = document.querySelector('video');
if (!video) return;
const handler = () => cleanRows();
video.addEventListener('seeked', handler);
video.addEventListener('timeupdate', handler);
}

function init() {
setupPanelObserver();
setupVideoListener();
}

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

})();

2、单行可拖动版

// ==UserScript==

// @name YouTube Stats (单行可拖动版 Kbps+MB/s 1.7)
// @namespace http://tampermonkey.net/
// @version 1.7
// @description 优化字符补位,缩短间距,显示更紧凑。
// @author jian
// @match ://www.youtube.com/
// @match ://m.youtube.com/
// @grant none
// @run-at document-start
// ==/UserScript==

(function () {
'use strict';

const POS_KEY = 'yt-custom-speed-pos';
const OVERLAY_ID = 'yt-custom-speed-overlay';

let lastKbps = "0";
let lastMBps = "0.00";
let lastBuffer = "0.0";
let panelActive = false;
let userClosed = false;
let lastNativeVisible = false;

const style = document.createElement('style');
style.textContent = `
.html5-video-info-panel { opacity: 0 !important; pointer-events: none !important; visibility: hidden !important; }
#${OVERLAY_ID} {
position: absolute;
padding: 5px 10px;
border-radius: 6px;
background: rgba(0,0,0,0.5);
color: #fff;
font-size: 12px;
font-family: 'Consolas', 'Monaco', monospace;
white-space: pre;
cursor: move;
user-select: none;
line-height: 1.2;
z-index: 999999;
display: none;
/ 进一步减小最小宽度,让框体紧贴文字 /
min-width: 220px;
align-items: center;
justify-content: space-between;
}
#${OVERLAY_ID} .close-btn {
margin-left: 8px;
font-size: 16px;
color: rgba(255,255,255,0.4);
cursor: pointer;
flex-shrink: 0;
line-height: 1;
}
#${OVERLAY_ID} .close-btn:hover { color: #ff4d4d; }
`;
document.documentElement.appendChild(style);

function calcMBps(kbps) {
return (kbps / 8 / 1024).toFixed(2);
}

function createOverlay() {
let box = document.getElementById(OVERLAY_ID);
if (!box) {
const player = document.querySelector('#movie_player') || document.querySelector('.html5-video-player');
if (!player) return null;

box = document.createElement('div');
box.id = OVERLAY_ID;

const textSpan = document.createElement('span');
textSpan.id = ${OVERLAY_ID}-text;
box.appendChild(textSpan);

const closeBtn = document.createElement('span');
closeBtn.className = 'close-btn';
closeBtn.textContent = '×';

closeBtn.addEventListener('click', (e) => {
e.stopPropagation();
userClosed = true;
const nativeCloseBtn = document.querySelector('.html5-video-info-panel button.html5-video-info-panel-close');
if (nativeCloseBtn) nativeCloseBtn.click();
box.style.display = 'none';
});

box.appendChild(closeBtn);
const pos = JSON.parse(localStorage.getItem(POS_KEY)) || { left: '20px', top: '20px' };
Object.assign(box.style, { left: pos.left, top: pos.top });
enableDrag(box);
player.appendChild(box);
}
return box;
}

function enableDrag(el) {
let dragging = false;
let startX, startY, startLeft, startTop;
const start = e => {
if (e.target.className === 'close-btn') return;
dragging = true;
const p = e.touches ? e.touches[0] : e;
startX = p.clientX; startY = p.clientY;
startLeft = el.offsetLeft; startTop = el.offsetTop;
e.stopPropagation();
};
const move = e => {
if (!dragging) return;
const p = e.touches ? e.touches[0] : e;
el.style.left = (startLeft + p.clientX - startX) + 'px';
el.style.top = (startTop + p.clientY - startY) + 'px';
};
const end = () => {
if (dragging) {
dragging = false;
localStorage.setItem(POS_KEY, JSON.stringify({ left: el.style.left, top: el.style.top }));
}
};
el.addEventListener('mousedown', start);
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', end);
}

function fetchData() {
const nativePanel = document.querySelector('.html5-video-info-panel');
const isNowVisible = !!(nativePanel && nativePanel.offsetParent !== null);

if (isNowVisible && !lastNativeVisible) userClosed = false;
lastNativeVisible = isNowVisible;
panelActive = isNowVisible;

if (nativePanel) {
const raw = nativePanel.textContent || "";
const sMatch = raw.match(/(\d+)\s*Kbps/i);
if (sMatch && sMatch[1]) {
lastKbps = sMatch[1];
lastMBps = calcMBps(parseInt(lastKbps));
}
const bMatch = raw.match(/([\d.]+)\s*s/);
if (bMatch && bMatch[1]) lastBuffer = bMatch[1];
}
}

function updateOverlay() {
const box = createOverlay();
if (!box) return;

if (!panelActive || userClosed) {
box.style.display = 'none';
return;
}

const textSpan = box.querySelector(#${OVERLAY_ID}-text);

// --- 紧凑对齐逻辑 ---
const kbpsStr = lastKbps.padStart(6, ' '); // 网速 6 位
const mbpsStr = lastMBps.padStart(5, ' '); // MB/s 5 位 (xx.xx)
const bufferStr = lastBuffer.padStart(5, ' '); // 缓冲 5 位 (xx.xx)

// 缩短符号间的空格
textSpan.textContent = 速度:${kbpsStr}Kbps → ${mbpsStr}MB/s 缓冲:${bufferStr}s;
box.style.display = 'flex';
}

setInterval(() => {
fetchData();
updateOverlay();
}, 300);

})();