您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
soop 방송 딜레이 1.5초 이내로 자동 조정
// ==UserScript== // @name soop 방송 딜레이 자동 조정 // @namespace https://greasyfork.org/ko/scripts/539405 // @version 1.3 // @description soop 방송 딜레이 1.5초 이내로 자동 조정 // @icon https://www.google.com/s2/favicons?sz=64&domain=www.sooplive.co.kr // @author 다크초코 // @match https://play.sooplive.co.kr/* // @license MIT // ==/UserScript== (function() { 'use strict'; const CONFIG = { CHECK_INTERVAL: 100, // 딜레이 체크 간격 (ms) HISTORY_DURATION: 2000, // 평균 계산 기간 (ms) - 최근 2000ms동안 계산된 딜레이의 평균값을 기준으로 딜레이 조정 TRIGGER_DELAY: 1500, // 자동 조정 시작 딜레이 (ms) - 1500ms 도달시 조절 시작 TARGET_DELAY: 1000, // 목표 딜레이 (ms) - 1000ms까지 조정 후 정상 속도 복귀 SPEED_LEVELS: [ { minDelay: 5000, playbackRate: 1.3 }, // 5초 이상 - 1.3배 { minDelay: 3000, playbackRate: 1.25 }, // 3초 이상 - 1.25배 { minDelay: 2500, playbackRate: 1.2 }, // 2.5초 이상 - 1.2배 { minDelay: 2000, playbackRate: 1.15 }, // 2초 이상 - 1.15배 { minDelay: 1500, playbackRate: 1.1 }, // 1.5초 이상 - 1.1배 { minDelay: 0, playbackRate: 1.05 } // 그 외 - 1.05배 ], NORMAL_RATE: 1.0 // 정상 재생 속도 }; let delayHistory = []; let isAdjusting = false; let checkInterval = null; let video = null; let currentPlaybackRate = 1.0; let fullscreenListenersAdded = false; let lastDisplayUpdate = 0; let cachedFullscreenState = false; let playbackRateProtection = false; let speedControlObserver = null; function findVideo() { return document.querySelector('video'); } function calculateDelay(videoElement) { if (!videoElement) return null; try { 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; } } catch (error) { console.warn('딜레이 계산 오류:', error); } 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 protectPlaybackRate() { if (!video || playbackRateProtection) return; playbackRateProtection = true; const originalPlaybackRateDescriptor = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate'); Object.defineProperty(video, 'playbackRate', { get: function() { return currentPlaybackRate; }, set: function(value) { if (arguments.callee.caller && arguments.callee.caller.toString().includes('adjustPlaybackRate')) { originalPlaybackRateDescriptor.set.call(this, value); currentPlaybackRate = value; } else { console.warn('외부 속도 제어 차단됨:', value); } }, configurable: true }); video.addEventListener('ratechange', function(e) { if (Math.abs(video.playbackRate - currentPlaybackRate) > 0.01) { e.preventDefault(); e.stopPropagation(); originalPlaybackRateDescriptor.set.call(video, currentPlaybackRate); } }, true); } function adjustPlaybackRate(rate) { if (!video) return; try { if (Math.abs(video.playbackRate - rate) > 0.01) { const originalSet = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate').set; originalSet.call(video, rate); currentPlaybackRate = rate; } } catch (error) { console.warn('재생 속도 조정 오류:', error); } } function updateFullscreenState() { cachedFullscreenState = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement); return cachedFullscreenState; } function displayDelayInfo(currentDelay, averageDelay) { const now = Date.now(); if (now - lastDisplayUpdate < 200) return; lastDisplayUpdate = now; try { let infoElement = document.getElementById('delay-info'); if (!infoElement) { infoElement = document.createElement('div'); infoElement.id = 'delay-info'; infoElement.style.cssText = ` position: fixed; bottom: 10px; right: 10px; background: rgba(0, 0, 0, 0.7); color: white; padding: 3px 5px; border-radius: 3px; font-family: monospace; font-size: 7pt; line-height: 1.2; z-index: 10000; opacity: 0.8; `; document.body.appendChild(infoElement); } infoElement.style.display = cachedFullscreenState ? 'none' : 'block'; const status = isAdjusting ? `${currentPlaybackRate}x` : '1.0x'; infoElement.textContent = `${averageDelay.toFixed(0)}ms ${status}`; } catch (error) { console.warn('모니터링창 업데이트 오류:', error); } } function handleFullscreenChange() { updateFullscreenState(); try { const infoElement = document.getElementById('delay-info'); if (infoElement) { infoElement.style.display = cachedFullscreenState ? 'none' : 'block'; } } catch (error) { console.warn('전체화면 변경 처리 오류:', error); } } function setupFullscreenListener() { if (fullscreenListenersAdded) return; try { const events = ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange']; events.forEach(event => { document.addEventListener(event, handleFullscreenChange); }); fullscreenListenersAdded = true; updateFullscreenState(); } catch (error) { console.warn('전체화면 리스너 설정 오류:', error); } } function removeFullscreenListener() { if (!fullscreenListenersAdded) return; try { const events = ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange']; events.forEach(event => { document.removeEventListener(event, handleFullscreenChange); }); fullscreenListenersAdded = false; } catch (error) { console.warn('전체화면 리스너 제거 오류:', error); } } function checkAndAdjustDelay() { if (!video) { video = findVideo(); if (!video) return; } const currentDelay = calculateDelay(video); if (currentDelay === null) return; addDelayToHistory(currentDelay); const averageDelay = getAverageDelay(); displayDelayInfo(currentDelay, averageDelay); 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 (Math.abs(newPlaybackRate - currentPlaybackRate) > 0.01) { adjustPlaybackRate(newPlaybackRate); } } } function cleanup() { try { if (checkInterval) { clearInterval(checkInterval); checkInterval = null; } removeFullscreenListener(); if (speedControlObserver) { speedControlObserver.disconnect(); speedControlObserver = null; } const infoElement = document.getElementById('delay-info'); if (infoElement && infoElement.parentNode) { infoElement.parentNode.removeChild(infoElement); } } catch (error) { console.warn('정리 작업 오류:', error); } } function resetState() { delayHistory = []; isAdjusting = false; video = null; currentPlaybackRate = 1.0; lastDisplayUpdate = 0; cachedFullscreenState = false; playbackRateProtection = false; } function startDelayAdjuster() { video = findVideo(); if (!video) { setTimeout(startDelayAdjuster, 1000); return; } try { checkInterval = setInterval(checkAndAdjustDelay, CONFIG.CHECK_INTERVAL); setupFullscreenListener(); protectPlaybackRate(); disableKeyboardShortcuts(); speedControlObserver = blockSpeedControlElements(); } catch (error) { console.warn('딜레이 조정기 시작 오류:', error); } } function disableKeyboardShortcuts() { document.addEventListener('keydown', function(e) { if (e.target.tagName.toLowerCase() === 'input' || e.target.tagName.toLowerCase() === 'textarea' || e.target.contentEditable === 'true') { return; } if (e.key === '<' || e.key === '>' || (e.shiftKey && (e.key === ',' || e.key === '.'))) { e.preventDefault(); e.stopPropagation(); console.warn('속도 제어 단축키 차단됨:', e.key); } }, true); } function blockSpeedControlElements() { const observer = new MutationObserver(() => { const speedControls = document.querySelectorAll('[class*="speed"], [class*="rate"], [id*="speed"], [id*="rate"]'); speedControls.forEach(element => { if (element.tagName === 'BUTTON' || element.tagName === 'SELECT' || element.type === 'range') { element.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); console.warn('속도 제어 UI 차단됨'); }, true); } }); }); observer.observe(document.body, { childList: true, subtree: true }); return observer; } 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; cleanup(); resetState(); setTimeout(startDelayAdjuster, 1000); } }); urlObserver.observe(document, { subtree: true, childList: true }); window.addEventListener('beforeunload', () => { cleanup(); urlObserver.disconnect(); }); })();