치지직 추가 볼륨

치지직 추가 증폭 볼륨을 구현합니다.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name          치지직 추가 볼륨
// @namespace     치지직 추가 볼륨
// @match         *://chzzk.naver.com/*
// @version       0.6
// @description   치지직 추가 증폭 볼륨을 구현합니다.
// @icon          https://www.google.com/s2/favicons?sz=256&domain_url=chzzk.naver.com
// @author        mickey90427 <[email protected]>
// @grant         none
// @license       MIT
// ==/UserScript==

(function() {
    'use strict';

    const maxVolume = 16; // 최대 볼륨 증폭 비율
    let gainValue = getSavedVolume() || 1; // 쿠키에서 저장된 볼륨 값 불러오기, 기본값 1
    const originalVolume = 1;
    let hideTimeout;

    // 볼륨 증폭 기능
    function boostVolume(video, boost) {
        let audioContext = video.audioContext;
        let gainNode = video.gainNode;

        if (!audioContext) {
            audioContext = new (window.AudioContext || window.webkitAudioContext)();
            let source = audioContext.createMediaElementSource(video);
            gainNode = audioContext.createGain();
            source.connect(gainNode);
            gainNode.connect(audioContext.destination);
            video.audioContext = audioContext;
            video.gainNode = gainNode;

            video.addEventListener('play', () => {
                audioContext.resume();
            }, { once: true });
        }

        gainNode.gain.value = boost ? gainValue : originalVolume;
    }

    // 증폭 적용 기능
    function applyVolumeBoost() {
        let videos = document.querySelectorAll('video');
        videos.forEach(video => {
            boostVolume(video, true);
        });
        saveVolume(gainValue); // 볼륨 값 저장
    }

    // 증폭 해제 기능
    function removeVolumeBoost() {
        let videos = document.querySelectorAll('video');
        videos.forEach(video => {
            boostVolume(video, false);
        });
    }

    // 슬라이더와 버튼을 페이지에 추가
    function addVolumeSlider() {
        const targetElement = document.querySelector('.pzp-pc__bottom-buttons-left');
        if (targetElement) {
            // 기존에 추가된 슬라이더가 있는지 확인
            if (document.querySelector('#volumeBoostSlider')) return;

            const container = document.createElement('div');
            container.style.display = 'flex';
            container.style.alignItems = 'center';
            container.style.marginLeft = '10px';
            container.style.opacity = '0'; // 초기에는 숨김
            container.style.transition = 'opacity 0.3s';

            const slider = document.createElement('input');
            slider.id = 'volumeBoostSlider';
            slider.type = 'range';
            slider.min = 1;
            slider.max = maxVolume;
            slider.value = gainValue;
            slider.style.width = '150px';
            slider.style.marginRight = '10px';
            slider.style.cursor = 'pointer';

            const label = document.createElement('span');
            label.id = 'volumeBoostLabel';
            label.textContent = `볼륨: ${((gainValue - 1) / (maxVolume - 1) * 100).toFixed(2)}%`;
            label.style.color = '#fff';

            slider.oninput = () => {
                gainValue = parseFloat(slider.value);
                const percentage = ((gainValue - 1) / (maxVolume - 1) * 100).toFixed(2);
                label.textContent = `볼륨: ${percentage}%`;
                applyVolumeBoost(); // 볼륨 적용
            };

            container.appendChild(slider);
            container.appendChild(label);
            container.id = 'volumeBoostControl';
            targetElement.parentNode.insertBefore(container, targetElement.nextSibling);

            // 슬라이더 표시 및 숨기기
            function showSlider() {
                container.style.opacity = '1'; // 슬라이더 표시
                resetHideTimeout();
            }

            function hideSlider() {
                container.style.opacity = '0'; // 슬라이더 숨김
            }

            function resetHideTimeout() {
                clearTimeout(hideTimeout);
                hideTimeout = setTimeout(hideSlider, 3000); // 3초 후 슬라이더 숨김
            }

            // 슬라이더가 있는 요소와 그 하위 요소들에 마우스가 들어가면 슬라이더를 보이게 하고 타이머를 리셋
            function setupEventListeners() {
                const videoContainer = document.querySelector('.webplayer-internal-video');
                const shadowElements = document.querySelectorAll('.pzp-pc__bottom-shadow, .pzp-pc__bottom');

                function onMouseMoveOrKeyDown(event) {
                    if (videoContainer.contains(event.target) || Array.from(shadowElements).some(el => el.contains(event.target))) {
                        showSlider();
                    } else {
                        hideSlider();
                    }
                }

                document.addEventListener('mousemove', onMouseMoveOrKeyDown);
                document.addEventListener('keydown', onMouseMoveOrKeyDown);
            }

            setupEventListeners();
        } else {
            console.log('targetElement를 찾을 수 없습니다.');
            // 일정 시간 후 재시도
            setTimeout(addVolumeSlider, 1000);
        }
    }

    // 쿠키에 볼륨 값 저장
    function saveVolume(value) {
        const domain = window.location.hostname;
        document.cookie = `volumeBoost_${domain}=${value}; path=/;`;
    }

    // 쿠키에서 볼륨 값 불러오기
    function getSavedVolume() {
        const domain = window.location.hostname;
        const name = `volumeBoost_${domain}=`;
        const decodedCookie = decodeURIComponent(document.cookie);
        const cookies = decodedCookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            let cookie = cookies[i];
            while (cookie.charAt(0) === ' ') {
                cookie = cookie.substring(1);
            }
            if (cookie.indexOf(name) === 0) {
                return parseFloat(cookie.substring(name.length, cookie.length));
            }
        }
        return null; // 쿠키에 저장된 볼륨 값이 없으면 null 반환
    }

    // 비디오가 로드된 후에 볼륨을 적용하는 함수
    function init() {
        function waitForVideoAndApply() {
            const videos = document.querySelectorAll('video');
            if (videos.length > 0) {
                applyVolumeBoost(); // 비디오가 존재할 때 볼륨 적용
                addVolumeSlider(); // 슬라이더 추가
            } else {
                // 비디오가 없으면 100ms 후에 다시 시도
                setTimeout(waitForVideoAndApply, 100);
            }
        }

        waitForVideoAndApply(); // 비디오 로드 확인 후 볼륨 적용 시작

        // MutationObserver를 사용하여 새로운 비디오 요소가 추가되었을 때 다시 시도
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.addedNodes.length > 0) {
                    waitForVideoAndApply(); // 비디오가 추가되었을 때 볼륨 적용 시도
                }
            });
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

    init();
})();