비디오 배속 조절 (개인용 특정사이트만 기능)

비디오 배속 조절 제한을 해제하고 최대 16배까지 조절 가능한 파란색 버튼을 제공합니다.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         비디오 배속 조절 (개인용 특정사이트만 기능)
// @namespace    dendenmushi
// @version     2.5.1
// @description 비디오 배속 조절 제한을 해제하고 최대 16배까지 조절 가능한 파란색 버튼을 제공합니다.
// @author      dendenmushi with Gemini
// @match       *://*/*
// @license     MIT
// @grant       none
// ==/UserScript==

(function() {
    'use strict';

    const SCRIPT_ID = "[Unlock Speed Control - Max Speed 16]";
    const DEFAULT_SPEED = 1.0;
    const MAX_SPEED = 16.0;
    const SPEED_INCREMENT = 2.0;
    let currentSpeed = DEFAULT_SPEED;
    const urlParams = new URLSearchParams(window.location.search);
    const numParam = urlParams.get('num');
    const chapterParam = urlParams.get('chapter');
    const paragraphParam = urlParams.get('paragraph');
    const contentIdParam = urlParams.get('content_id');

    const styleCode = `
        :root {
            --md-sys-color-primary-container: #004880;
            --md-sys-color-on-primary-container: #d1e4ff;
            --toggle-size: 56px;
            --toggle-opacity: 1.0;
        }
        #mes-blue-button {
            position: fixed !important;
            top: 50% !important;
            right: 20px !important;
            transform: translateY(-50%) !important;
            z-index: 2147483646 !important;
            background-color: var(--md-sys-color-primary-container) !important;
            color: var(--md-sys-color-on-primary-container) !important;
            opacity: var(--toggle-opacity) !important;
            width: var(--toggle-size) !important;
            height: var(--toggle-size) !important;
            border-radius: 18px !important;
            border: none !important;
            cursor: pointer !important;
            box-shadow: 0 6px 10px 0 rgba(0,0,0,0.14), 0 1px 18px 0 rgba(0,0,0,0.12), 0 3px 5px -1px rgba(0,0,0,0.20) !important;
            transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.2s ease, opacity 0.3s ease;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            overflow: hidden !important;
            backface-visibility: hidden;
            -webkit-backface-visibility: hidden;
            -webkit-tap-highlight-color: transparent !important;
            font-size: 16px;
            font-weight: bold;
        }
        #mes-blue-button:active {
            transform: translateY(-50%) scale(0.95) !important;
            box-shadow: 0 2px 4px -1px rgba(0,0,0,0.2), 0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12) !important;
        }
        .jp-progress,
        .jp-play-bar,
        .jp-seek-bar {
            pointer-events: auto !important;
        }
    `;

    const injectedScriptCode = `
        const SCRIPT_ID = "[AdGuard Inject Script - Speed Control + Selective Block - No Alert - Max Speed 16 - Increment x2]";
        const DEFAULT_SPEED = 1.0;
        const MAX_SPEED = 16.0;
        const SPEED_INCREMENT = 2.0;
        let currentSpeed = DEFAULT_SPEED;
        const urlParams = new URLSearchParams(window.location.search);
        const numParam = urlParams.get('num');
        const chapterParam = urlParams.get('chapter');
        const paragraphParam = urlParams.get('paragraph');
        const contentIdParam = urlParams.get('content_id');

        function findVideo() {
            return document.querySelector('video');
        }

        const blueButton = document.getElementById('mes-blue-button');
        const conarrInput = document.getElementById('conarr');

        window.onload = null;

        function triggerConarr() {
            if (conarrInput && numParam && chapterParam && paragraphParam && contentIdParam) {
                const num = parseInt(numParam, 10);
                if (!isNaN(num) && typeof window.conarr === 'function') {
                    setTimeout(() => {
                        const pageInfo = chapterParam + "_" + paragraphParam;
                        const currentPageInfo = paragraphParam;
                        sendParentPageInfo(pageInfo, currentPageInfo);
                        console.log(SCRIPT_ID, "페이지 정보 추출 및 부모 프레임에 전송 (사용자 스크립트)");
                    }, 1000);
                } else {
                    console.warn(SCRIPT_ID, "URL 파라미터 'num'이 유효하지 않거나 window.conarr 함수를 찾을 수 없습니다.");
                }
            } else {
                console.warn(SCRIPT_ID, "'conarr' 요소 또는 필요한 URL 파라미터를 찾을 수 없습니다.");
            }
        }

        function setPlaysInline() {
            const video = findVideo();
            if (video) {
                video.setAttribute('playsinline', true);
                console.log(SCRIPT_ID, "playsinline 속성 설정 (사용자 스크립트)");
            } else {
                console.warn(SCRIPT_ID, "비디오 요소를 찾을 수 없어 playsinline 속성을 설정할 수 없습니다.");
            }
        }

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

        if (blueButton) {
            blueButton.addEventListener('click', () => {
                const video = findVideo();
                if (video) {
                    currentSpeed *= SPEED_INCREMENT;
                    if (currentSpeed > MAX_SPEED) {
                        currentSpeed = DEFAULT_SPEED;
                    }
                    video.playbackRate = currentSpeed;
                    blueButton.textContent = \`x\${currentSpeed.toFixed(2)}\`;
                    console.log(SCRIPT_ID, \`비디오 재생 속도가 \${currentSpeed.toFixed(2)}로 변경되었습니다.\`);
                } else {
                    console.error(SCRIPT_ID, '비디오 요소를 찾을 수 없습니다.');
                }
            });
            console.log(SCRIPT_ID, "파란색 버튼 클릭 이벤트 리스너가 추가되었습니다.");
        } else {
            console.error(SCRIPT_ID, "파란색 버튼 요소를 찾을 수 없습니다.");
        }

        function sendParentPageInfo(pageInfo, currentPageInfo) {
            if (window.parent && typeof window.parent._setPageInfo === 'function' && typeof window.parent._setCurrentLocation === 'function' && typeof window.parent._progressSave === 'function') {
                window.parent._setPageInfo(pageInfo);
                window.parent._setCurrentLocation(currentPageInfo);
                window.parent._progressSave();
                console.log(SCRIPT_ID, "부모 프레임에 페이지 정보 전송 (사용자 스크립트)");
            } else {
                console.warn(SCRIPT_ID, "부모 프레임과의 통신 함수를 찾을 수 없습니다.");
            }
        }
    `;

    function initializeScript() {
        const styleElement = document.createElement('style');
        styleElement.textContent = styleCode;
        document.head.appendChild(styleElement);

        const blueButton = document.createElement('button');
        blueButton.id = 'mes-blue-button';
        blueButton.textContent = `x${DEFAULT_SPEED}`;
        blueButton.setAttribute('aria-label', '비디오 배속 조절 버튼');
        document.body.appendChild(blueButton);

        const scriptElement = document.createElement('script');
        scriptElement.type = 'text/javascript';
        scriptElement.textContent = injectedScriptCode;
        document.body.appendChild(scriptElement);

        console.log(SCRIPT_ID, "자바스크립트 코드와 파란색 버튼이 동적으로 삽입되었습니다.");
    }

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