soop 방송 딜레이 자동 조정

soop 방송 딜레이 2.5초 이내로 자동 조정

当前为 2025-06-14 提交的版本,查看 最新版本

// ==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();
    });

})();