您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
soop 방송 딜레이 2.5초 이내로 자동 조정
当前为
// ==UserScript== // @name soop 방송 딜레이 자동 조정 // @namespace https://greasyfork.org/ko/scripts/539405 // @version 1.0 // @description soop 방송 딜레이 2.5초 이내로 자동 조정 // @author 다크초코 // @match https://play.sooplive.co.kr/* // @match https://vod.sooplive.co.kr/* // @license MIT // ==/UserScript== (function() { 'use strict'; const CONFIG = { CHECK_INTERVAL: 100, // 딜레이 체크 간격 (ms) HISTORY_DURATION: 2000, // 평균 계산 기간 (ms) - 최근 2000ms동안 계산된 딜레이의 평균값을 기준으로 딜레이 조정 TRIGGER_DELAY: 2500, // 자동 조정 시작 딜레이 (ms) - 2500ms 도달시 조절 시작(튀는 딜레이 값 방지) TARGET_DELAY: 1900, // 목표 딜레이 (ms) - 시작 딜레이보다 500ms이상 더 짧게 설정 추천 SPEED_LEVELS: [ { minDelay: 5000, playbackRate: 1.3 }, // 5초 이상 - 1.3배 { minDelay: 4000, playbackRate: 1.25 }, // 4초 이상 { minDelay: 3500, playbackRate: 1.2 }, // 3.5초 이상 { minDelay: 3000, playbackRate: 1.15 }, // 3초 이상 { minDelay: 2500, playbackRate: 1.1 }, // 2.5초 이상 { minDelay: 0, playbackRate: 1.05 } // 그 외 ], NORMAL_RATE: 1.0 // 정상 재생 속도 }; let delayHistory = []; let isAdjusting = false; let checkInterval = null; let video = null; let currentPlaybackRate = 1.0; function findVideo() { return document.querySelector('video'); } function calculateDelay(videoElement) { if (!videoElement) return null; const buffered = videoElement.buffered; if (buffered.length > 0) { const bufferedEnd = buffered.end(buffered.length - 1); const currentTime = videoElement.currentTime; const delay = bufferedEnd - currentTime; return delay >= 0 ? delay * 1000 : null; } return null; } function addDelayToHistory(delay) { const now = Date.now(); delayHistory.push({ delay, timestamp: now }); delayHistory = delayHistory.filter(item => now - item.timestamp <= CONFIG.HISTORY_DURATION ); } function getAverageDelay() { if (delayHistory.length === 0) return 0; const totalDelay = delayHistory.reduce((sum, item) => sum + item.delay, 0); return totalDelay / delayHistory.length; } function getPlaybackRate(averageDelay) { for (const config of CONFIG.SPEED_LEVELS) { if (averageDelay >= config.minDelay) { return config.playbackRate; } } return CONFIG.SPEED_LEVELS[CONFIG.SPEED_LEVELS.length - 1].playbackRate; } function adjustPlaybackRate(rate) { if (video && video.playbackRate !== rate) { video.playbackRate = rate; currentPlaybackRate = rate; } } function displayDelayInfo(currentDelay, averageDelay) { } function checkAndAdjustDelay() { if (!video) { video = findVideo(); if (!video) return; } const currentDelay = calculateDelay(video); if (currentDelay === null) return; addDelayToHistory(currentDelay); const averageDelay = getAverageDelay(); if (delayHistory.length < 10) return; if (!isAdjusting && averageDelay >= CONFIG.TRIGGER_DELAY) { isAdjusting = true; const playbackRate = getPlaybackRate(averageDelay); adjustPlaybackRate(playbackRate); } else if (isAdjusting && averageDelay <= CONFIG.TARGET_DELAY) { isAdjusting = false; adjustPlaybackRate(CONFIG.NORMAL_RATE); } else if (isAdjusting) { const newPlaybackRate = getPlaybackRate(averageDelay); if (newPlaybackRate !== currentPlaybackRate) { adjustPlaybackRate(newPlaybackRate); } } } function startDelayAdjuster() { video = findVideo(); if (!video) { setTimeout(startDelayAdjuster, 1000); return; } if (checkInterval) { clearInterval(checkInterval); } checkInterval = setInterval(checkAndAdjustDelay, CONFIG.CHECK_INTERVAL); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startDelayAdjuster); } else { startDelayAdjuster(); } let currentUrl = location.href; const urlObserver = new MutationObserver(() => { if (location.href !== currentUrl) { currentUrl = location.href; if (checkInterval) { clearInterval(checkInterval); checkInterval = null; } delayHistory = []; isAdjusting = false; video = null; currentPlaybackRate = 1.0; setTimeout(startDelayAdjuster, 1000); } }); urlObserver.observe(document, { subtree: true, childList: true }); window.addEventListener('beforeunload', () => { if (checkInterval) { clearInterval(checkInterval); } urlObserver.disconnect(); }); })();