您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
YouTube Live の遅延を自動的に最小化します
当前为
// ==UserScript== // @name YouTube Live minimum latency // @description YouTube Live の遅延を自動的に最小化します // @namespace https://gitlab.com/sigsign // @version 0.1.0 // @author Sigsign // @license MIT or Apache-2.0 // @match https://www.youtube.com/* // @noframes // @grant none // ==/UserScript== (function () { 'use strict'; function loadPage(fn) { /** * YouTube は SPA になっているため load だけではページ遷移を捕捉できない。 * load と yt-page-data-updated を併用するか yt-navigate-finish を使う。 * * See: https://stackoverflow.com/questions/24297929/ */ document.addEventListener('yt-navigate-finish', fn, false); } function getPlayer() { return document.querySelector('#movie_player'); } function getLiveLatency(player) { const current = Date.now() / 1000; const time = player.getMediaReferenceTime(); return time ? current - time : 0; } function getBufferHealth(player) { const stats = player.getVideoStats(); if (!stats) { return; } const buffers = stats.vbu; if (!buffers) { return; } const [buffer] = buffers.split('-').reverse(); const bufferTime = Number(buffer); const currentTime = Number(stats.vct); if (isNaN(bufferTime) || isNaN(currentTime)) { return; } return bufferTime - currentTime; } const thresholds = { NORMAL: 6.0, LOW: 3.0, ULTRALOW: 1.0, }; loadPage(() => { let isAccelerated = false; const player = getPlayer(); if (!player) { return; } const stats = player.getVideoStats(); if (stats.live !== 'live' && stats.live !== 'dvr' && stats.live !== 'lp') { return; } if (!player.getAvailablePlaybackRates().includes(1.25)) { return; } if (typeof stats.latency_class === 'undefined') { return; } const threshold = stats.live === 'lp' ? 12.5 : thresholds[stats.latency_class]; if (typeof threshold !== 'number') { return; } const video = document.querySelector('video'); if (!video) { return; } const startAcceleration = () => { const rate = player.getPlaybackRate(); if (rate !== 1.0) { return; } const latency = getLiveLatency(player); if (stats.live !== 'live' && latency > 180) { /** * DVR or プレミア公開、かつ遅延が 3 分以上ある場合、 * ユーザーが自分の意思でシークしていると想定して対象から外す。 */ return; } if (latency > threshold + 0.5) { player.setPlaybackRate(1.25); isAccelerated = true; } }; const stopAcceleration = () => { if (!isAccelerated) { return; } const rate = player.getPlaybackRate(); if (rate !== 1.25) { return; } const bufferLimit = stats.latency_class === 'ULTRALOW' ? 0.8 : 0.4; const buffer = getBufferHealth(player) || 1.0; const latency = getLiveLatency(player); if (buffer < bufferLimit || latency < threshold) { player.setPlaybackRate(1.0); } }; const updateFlag = () => { if (!isAccelerated) { return; } const rate = player.getPlaybackRate(); if (rate !== 1.25) { isAccelerated = false; } }; video.addEventListener('playing', startAcceleration); video.addEventListener('timeupdate', stopAcceleration); video.addEventListener('ratechange', updateFlag); const timer = setInterval(startAcceleration, 120 * 1000); const eventCleaner = () => { video.removeEventListener('playing', startAcceleration); video.removeEventListener('timeupdate', stopAcceleration); video.removeEventListener('ratechange', updateFlag); clearInterval(timer); }; document.addEventListener('yt-navigate-start', eventCleaner, { once: true }); }); }());