Greasy Fork

YouTube Split

Устанавливает сплит по времени (только после нажатия "НАЧАТЬ СПЛИТ"), выводит панель управления под видео с кнопками настройки и громкости, показывает оверлей "СПЛИТ НЕ ОПЛАЧЕН" с кнопками продления и проигрывает звук при достижении порога. Исправлены ошибки.

目前为 2025-04-18 提交的版本。查看 最新版本

// ==UserScript==
// @name         YouTube Split
// @namespace    http://tampermonkey.net/
// @version      1.9 // Повышаем версию
// @author       sosal
// @match        *://www.youtube.com/watch*
// @grant        none
// @description Устанавливает сплит по времени (только после нажатия "НАЧАТЬ СПЛИТ"), выводит панель управления под видео с кнопками настройки и громкости, показывает оверлей "СПЛИТ НЕ ОПЛАЧЕН" с кнопками продления и проигрывает звук при достижении порога. Исправлены ошибки.
// ==/UserScript==

(function() {
    'use strict';

    // --- Конфигурация ---
    let splitMinutes = null;    // Значение сплита в минутах, по умолчанию null (сплит не активен)
    const extendCost = 300;     // Стоимость продления +1 минуты в условных рублях
    // ВСТАВЬТЕ СЮДА ПРЯМУЮ ССЫЛКУ НА ВАШ ЗВУК!
    const splitSoundUrl = 'https://github.com/lardan099/donat/raw/refs/heads/main/alert_orig.mp3';
    const localStorageVolumeKey = 'ytSplitAlertVolume'; // Ключ для сохранения громкости в localStorage


    // --- Глобальные переменные состояния ---
    let video = null;           // Элемент видео
    let overlay = null;         // Элемент оверлея
    let splitTriggered = false; // Флаг, что сплит активирован (видео остановлено)
    let audioPlayer = null;     // Для проигрывания звука
    let splitCheckIntervalId = null; // ID интервала проверки сплита (для логики сплита)
    let setupIntervalId = null;      // ID интервала для поиска элементов и добавления панели
    let panelAdded = false; // Флаг, чтобы добавлять панель только один раз

    // --- CSS Стили для улучшения внешнего вида ---
    const styles = `
        /* Стили для панели управления */
        #split-control-panel {
            margin-top: 10px;
            margin-bottom: 15px;
            padding: 10px 15px;
            background: var(--yt-spec-badge-chip-background);
            border: 1px solid var(--yt-spec-border-div);
            border-radius: 8px;
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: 10px 20px; /* Увеличим горизонтальный разрыв */
            color: var(--yt-spec-text-primary);
            font-family: "Roboto", Arial, sans-serif;
            font-size: 14px;
            max-width: var(--ytd-watch-flexy-width);
            width: 100%;
            box-sizing: border-box;
        }

        ytd-watch-flexy:not([use- Sarkis]) #primary #split-control-panel {
            margin-left: auto;
            margin-right: auto;
        }

        #split-control-panel label {
            font-weight: 500;
            color: var(--yt-spec-text-secondary);
            flex-shrink: 0;
            line-height: 1.3;
        }

        #split-control-panel label i {
             font-style: normal;
             font-size: 12px;
             color: var(--yt-spec-text-disabled);
        }

         /* Контейнер для поля ввода и кнопок модификации */
        #split-input-group {
            display: flex;
            align-items: center;
            gap: 5px;
        }

        #split-control-panel input[type="number"] {
            width: 60px;
            padding: 8px 10px;
            background: var(--yt-spec-filled-button-background);
            color: var(--yt-spec-text-primary);
            border: 1px solid var(--yt-spec-action-simulate-border);
            border-radius: 4px;
            text-align: center;
            font-size: 15px;
            -moz-appearance: textfield;
        }

        #split-control-panel input[type="number"]::-webkit-outer-spin-button,
        #split-control-panel input[type="number"]::-webkit-inner-spin-button {
            -webkit-appearance: none;
            margin: 0;
        }

        /* Стили для всех кнопок внутри панели */
        #split-control-panel button {
            padding: 8px 15px;
            font-size: 15px;
            cursor: pointer;
            background: var(--yt-spec-grey-1);
            color: var(--yt-spec-text-primary);
            border: none;
            border-radius: 4px;
            transition: background 0.2s ease-in-out;
            font-weight: 500;
            flex-shrink: 0;
        }

        #split-control-panel button:hover {
             background: var(--yt-spec-grey-2);
        }

         /* Стили для кнопок модификации сплита (+1, +5 и т.д.) */
        #split-input-group button {
            padding: 8px 10px;
            font-size: 14px;
            background: var(--yt-spec-filled-button-background);
            border: 1px solid var(--yt-spec-action-simulate-border);
        }
         #split-input-group button:hover {
            background: var(--yt-spec-grey-2);
         }

        #split-control-panel button#set-split-button {
             background: var(--yt-spec-brand-suggested-action);
             color: var(--yt-spec-text-reverse);
             order: -1;
             margin-right: auto;
        }
         #split-control-panel button#set-split-button:hover {
             background: var(--yt-spec-brand-suggested-action-hover);
        }

        /* Стили для регулятора громкости */
        #split-volume-control {
             display: flex;
             align-items: center;
             gap: 5px;
        }
         #split-volume-control label {
             font-weight: 500;
             color: var(--yt-spec-text-secondary);
             flex-shrink: 0;
             line-height: normal; /* Сброс line-height для этой метки */
         }
         #split-volume-control input[type="range"] {
             flex-grow: 1; /* Занимает доступное место */
             min-width: 80px; /* Минимальная ширина слайдера */
             -webkit-appearance: none; /* Удаляем стандартный стиль Chrome/Safari */
             appearance: none;
             height: 8px; /* Высота трека */
             background: var(--yt-spec-grey-1);
             outline: none;
             opacity: 0.7;
             transition: opacity .2s;
             border-radius: 4px;
         }
         #split-volume-control input[type="range"]:hover {
             opacity: 1;
         }

         /* Стили для ползунка (Chrome/Safari) */
        #split-volume-control input[type="range"]::-webkit-slider-thumb {
             -webkit-appearance: none;
             appearance: none;
             width: 15px; /* Ширина ползунка */
             height: 15px; /* Высота ползунка */
             background: var(--yt-spec-brand-button-background); /* Цвет ползунка */
             cursor: pointer;
             border-radius: 50%; /* Круглый ползунок */
        }

        /* Стили для ползунка (Firefox) */
        #split-volume-control input[type="range"]::-moz-range-thumb {
             width: 15px;
             height: 15px;
             background: var(--yt-spec-brand-button-background);
             cursor: pointer;
             border-radius: 50%;
        }


        /* Стили для оверлея */
        #split-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.95);
            color: white;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            z-index: 99999;
            font-family: "Roboto", Arial, sans-serif;
            text-align: center;
            padding: 20px;
            box-sizing: border-box;
        }

        #split-overlay #split-warning-message {
            font-size: clamp(24px, 4vw, 36px);
            margin-bottom: 15px;
            color: yellow;
            font-weight: bold;
            text-shadow: 0 0 8px rgba(255, 255, 0, 0.5);
        }

        #split-overlay #split-main-message {
            font-size: clamp(40px, 8vw, 72px);
            font-weight: bold;
            margin-bottom: 40px;
            color: red;
            text-shadow: 0 0 15px rgba(255, 0, 0, 0.7);
        }

        #split-extend-buttons {
            display: flex;
            gap: 15px;
            flex-wrap: wrap;
            justify-content: center;
        }

        #split-extend-buttons button {
            padding: 12px 25px;
            font-size: clamp(18px, 3vw, 24px);
            cursor: pointer;
            background: var(--yt-spec-red-500);
            border: none;
            color: white;
            border-radius: 4px;
            font-weight: bold;
            transition: background 0.2s ease-in-out;
        }

        #split-extend-buttons button:hover {
            background: var(--yt-spec-red-600);
        }
    `;

    // --- Вспомогательная функция для внедрения CSS ---
    function injectStyles() {
        if (document.getElementById('yt-split-styles')) {
            return;
        }
        const styleElement = document.createElement("style");
        styleElement.id = 'yt-split-styles';
        styleElement.textContent = styles;
        document.head.appendChild(styleElement);
         console.log("YouTube Split: Стили внедрены.");
    }

    // Обновление отображения значения сплита в спинере
    // Эта функция теперь вызывается только при добавлении панели и при изменении global splitMinutes
    function updateSplitDisplay() {
        const inputField = document.getElementById("split-input");
        if (inputField) {
            inputField.valueAsNumber = splitMinutes === null ? 0 : splitMinutes;
        }
    }

     // Функция для изменения значения в поле ввода сплита
     // Эта функция вызывается кнопками +/- и меняет ТОЛЬКО ПОЛЕ ВВОДА
     function modifySplitInput(minutesToModify) {
         const inputField = document.getElementById("split-input");
         if (!inputField) return;

         let currentVal = inputField.valueAsNumber;

         if (isNaN(currentVal)) {
             currentVal = 0;
         }

         let newVal = currentVal + minutesToModify;

         if (newVal < 0) {
             newVal = 0;
         }

         inputField.valueAsNumber = newVal;
     }

    // Функция для запуска интервала проверки сплита
    function startSplitCheckInterval() {
        if (!splitCheckIntervalId) {
            splitCheckIntervalId = setInterval(checkSplitCondition, 500);
             console.log("YouTube Split: Запущена проверка сплита (interval).");
        } else {
             // console.log("YouTube Split: Проверка сплита уже запущена.");
        }
    }

    // Функция для остановки интервала проверки сплита
    function stopSplitCheckInterval() {
        if (splitCheckIntervalId) {
            clearInterval(splitCheckIntervalId);
            splitCheckIntervalId = null;
             console.log("YouTube Split: Проверка сплита остановлена.");
        }
    }

    // Создание панели управления сплитом и вставка ее под видео
    // Эта функция вызывается, когда нужный контейнер уже найден и panelAdded === false
    function addControlPanel(primaryContainer) {
        if (panelAdded) { // Дополнительная проверка, хотя setupElementsAndPanel тоже проверяет
            ensurePanelPosition(); // Убедимся в позиции существующей панели
            updateSplitDisplay(); // Обновим поле ввода на существующей панели
            return;
        }

        if (!primaryContainer) {
             console.error("YouTube Split: addControlPanel вызвана без primaryContainer!");
             return;
        }

        const panel = document.createElement("div");
        panel.id = "split-control-panel";

        // --- Кнопка "НАЧАТЬ СПЛИТ" ---
        const setButton = document.createElement("button");
        setButton.id = "set-split-button";
        setButton.textContent = "НАЧАТЬ СПЛИТ";
        setButton.addEventListener("click", function() {
            const inputField = document.getElementById("split-input");
            const inputVal = inputField.valueAsNumber;

            if (!isNaN(inputVal) && inputVal >= 0) {
                const oldSplitMinutes = splitMinutes; // Сохраняем для логики продолжения видео
                splitMinutes = inputVal; // Устанавливаем активный сплит из поля ввода
                // updateSplitDisplay(); // Не вызываем здесь, чтобы не мешать modifySplitInput

                 if (splitMinutes > 0) {
                     startSplitCheckInterval(); // Запускаем проверку если сплит > 0

                     // Проверяем условие сразу после установки, только если видео найдено
                     if (video) {
                         const thresholdSeconds = splitMinutes * 60;
                         if (video.currentTime >= thresholdSeconds) {
                             video.pause();
                             splitTriggered = true;
                             showOverlay();
                             if(splitSoundUrl && audioPlayer && audioPlayer.src !== 'ВАША_ПРЯМАЯ_ССЫЛКА_НА_ЗВУКОВОЙ_ФАЙЛ_ТУТ'){
                                 audioPlayer.pause();
                                 audioPlayer.currentTime = 0;
                                 audioPlayer.play().catch(e => console.error("YouTube Split: Ошибка при воспроизведении звука:", e));
                             }
                         } else {
                             splitTriggered = false;
                             removeOverlay();
                              // Если видео было на паузе *до* нажатия "НАЧАТЬ СПЛИТ" (т.е. сплит был null), и новое время сплита не достигнуто, запускаем
                             if (video.paused && oldSplitMinutes === null) {
                                 video.play();
                             }
                         }
                     }

                 } else { // splitMinutes === 0
                     stopSplitCheckInterval();
                     splitTriggered = false;
                     removeOverlay();
                     if (video && video.paused && oldSplitMinutes !== null && oldSplitMinutes > 0) {
                         video.play();
                     }
                 }
                 // Убеждаемся, что поле ввода показывает актуальное *активное* значение после нажатия
                 updateSplitDisplay();


            } else {
                alert("Введите корректное число минут.");
            }
        });

        // --- Метка "Сплит (мин):" (Исправление TrustedHTML) ---
        const label = document.createElement("label");
        label.setAttribute("for", "split-input");

        const labelTextMain = document.createTextNode("Сплит (мин):");
        const breakElement = document.createElement("br");
        const italicElement = document.createElement("i");
        const labelTextInstruction = document.createTextNode("(уст. перед \"Начать\")");

        label.appendChild(labelTextMain);
        label.appendChild(breakElement);
        italicElement.appendChild(labelTextInstruction);
        label.appendChild(italicElement);
        // --- Конец Исправления TrustedHTML ---


        // --- Группа ввода сплита и +/- кнопок ---
        const inputGroup = document.createElement("div");
        inputGroup.id = "split-input-group";

        const inputField = document.createElement("input");
        inputField.type = "number";
        inputField.id = "split-input";
        inputField.min = "0";
        // Начальное значение будет установлено функцией updateSplitDisplay при добавлении
        inputField.valueAsNumber = splitMinutes === null ? 0 : splitMinutes;

         const modifyButtons = [
             { text: '-10', minutes: -10 },
             { text: '-5', minutes: -5 },
             { text: '-1', minutes: -1 },
             { text: '+1', minutes: 1 },
             { text: '+5', minutes: 5 },
             { text: '+10', minutes: 10 },
             { text: '+20', minutes: 20 }
         ];

         modifyButtons.forEach(btnInfo => {
             const button = document.createElement("button");
             button.textContent = btnInfo.text;
             button.addEventListener("click", () => modifySplitInput(btnInfo.minutes));
             inputGroup.appendChild(button);
         });

        inputGroup.insertBefore(inputField, inputGroup.children[0]);


        // --- Регулятор громкости алерта ---
        const volumeControlGroup = document.createElement("div");
        volumeControlGroup.id = "split-volume-control";

        const volumeLabel = document.createElement("label");
        volumeLabel.setAttribute("for", "split-volume-slider");
        volumeLabel.textContent = "Громкость алерта:";

        const volumeSlider = document.createElement("input");
        volumeSlider.type = "range";
        volumeSlider.id = "split-volume-slider";
        volumeSlider.min = "0";
        volumeSlider.max = "1"; // HTMLMediaElement volume is typically 0 to 1
        volumeSlider.step = "0.05"; // Adjust step for finer control

         // Установка начального значения громкости из localStorage (или дефолт)
        let savedVolume = localStorage.getItem(localStorageVolumeKey);
        if (savedVolume === null) {
             savedVolume = '0.5'; // Громкость по умолчанию 50%
        }
        volumeSlider.value = savedVolume;

        // Обработчик изменения громкости
        volumeSlider.addEventListener("input", function() {
             // Применяем громкость к аудиоплееру, если он есть
             if (audioPlayer) {
                 audioPlayer.volume = this.value;
             }
             // Сохраняем значение в localStorage
             localStorage.setItem(localStorageVolumeKey, this.value);
        });

        volumeControlGroup.appendChild(volumeLabel);
        volumeControlGroup.appendChild(volumeSlider);

        // Применяем громкость к аудиоплееру сразу после создания панели,
        // т.к. audioPlayer мог быть создан до панели.
        if (audioPlayer) {
             audioPlayer.volume = savedVolume;
        }


        // --- Собираем панель ---
        panel.appendChild(setButton);
        panel.appendChild(label);
        panel.appendChild(inputGroup);
        panel.appendChild(volumeControlGroup); // Добавляем регулятор громкости


        // Вставляем панель в найденный контейнер (#primary) как первый дочерний элемент
        primaryContainer.insertBefore(panel, primaryContainer.firstChild);
        panelAdded = true; // Устанавливаем флаг, что панель добавлена
        console.log("YouTube Split: Панель управления добавлена под видео.");

        // Убедимся, что поле ввода показывает актуальное значение после добавления
        updateSplitDisplay();
    }

     // Функция для проверки и корректировки позиции панели
     // Вызывается из setupElementsAndPanel
     function ensurePanelPosition() {
         if (!panelAdded) return; // Проверяем позицию только если панель добавлена

         const panel = document.getElementById("split-control-panel");
         const primaryContainer = document.querySelector("ytd-watch-flexy #primary");

         if (panel && primaryContainer) {
             if (primaryContainer.firstChild !== panel) {
                  console.log("YouTube Split: Панель сместилась, перемещаем обратно.");
                  primaryContainer.insertBefore(panel, primaryContainer.firstChild);
             }
         }
          // Если панель есть, но контейнера нет, setupElementsAndPanel ее удалит.
     }


    function addMinutesToActiveSplit(minutesToAdd) {
        if (splitMinutes === null) return;

        splitMinutes += minutesToAdd;
        updateSplitDisplay(); // Обновляем поле ввода на панели

        const thresholdSeconds = splitMinutes * 60;

        if (video && video.currentTime < thresholdSeconds) {
            removeOverlay();
            splitTriggered = false;
            video.play();
        }
         // Оверлей остается, если продления недостаточно
    }

    function checkSplitCondition() {
        // Ищем видео каждый раз, на случай его пересоздания YouTube
        if (!video) {
            video = document.querySelector("video");
            if (!video) {
                 console.log("YouTube Split: Видеоэлемент не найден в checkSplitCondition, останавливаем проверку сплита.");
                 stopSplitCheckInterval();
                 splitTriggered = false;
                 removeOverlay();
                 // Панель и аудио плеер будут обработаны основным setupIntervalId или urlObserver
                 return;
            }
             // Убеждаемся, что аудио плеер есть и громкость применена
             initAudioPlayer();
             const volumeSlider = document.getElementById('split-volume-slider');
             if(audioPlayer && volumeSlider) audioPlayer.volume = volumeSlider.value;
        }

        // Проверяем условие сплита только если splitMinutes активно (не null и > 0)
        if (splitMinutes !== null && splitMinutes > 0) {
            const thresholdSeconds = splitMinutes * 60;

            if (video.currentTime >= thresholdSeconds && !splitTriggered) {
                video.pause();
                splitTriggered = true;
                showOverlay();
                 // Воспроизводим звук при первом триггере
                if(splitSoundUrl && audioPlayer && audioPlayer.src !== 'ВАША_ПРЯМАЯ_ССЫЛКА_НА_ЗВУКОВОЙ_ФАЙЛ_ТУТ'){
                     audioPlayer.pause();
                     audioPlayer.currentTime = 0;
                     audioPlayer.play().catch(e => console.error("YouTube Split: Ошибка при воспроизведении звука:", e));
                }
            }
            if (splitTriggered && video.currentTime < thresholdSeconds) {
                removeOverlay();
                splitTriggered = false;
                video.play();
            }
        } else {
             // Если splitMinutes стал null или 0 во время работы проверки
             stopSplitCheckInterval();
             splitTriggered = false;
             removeOverlay();
             if (video && video.paused) video.play();
        }
        // ensurePanelPosition() убрана отсюда
    }

    function showOverlay() {
        if (overlay) return;

        overlay = document.createElement("div");
        overlay.id = "split-overlay";

        const warningMessage = document.createElement("div");
        warningMessage.id = "split-warning-message";
        warningMessage.textContent = "⚠️ НУЖНО ДОНАТНОЕ ТОПЛИВО ⚠️";

        const splitMessage = document.createElement("div");
        splitMessage.id = "split-main-message";
        splitMessage.textContent = "СПЛИТ НЕ ОПЛАЧЕН";

        const extendButtonsContainer = document.createElement("div");
        extendButtonsContainer.id = "split-extend-buttons";

        const extendButtonConfigs = [
             { minutes: 1, cost: extendCost },
             { minutes: 5, cost: extendCost * 5 },
             { minutes: 10, cost: extendCost * 10 },
             { minutes: 20, cost: extendCost * 20 }
        ];

        extendButtonConfigs.forEach(config => {
            const button = document.createElement("button");
            button.textContent = `+ ${config.minutes} минут${getMinuteEnding(config.minutes)} - ${config.cost} рублей`;
            button.addEventListener("click", function() {
                addMinutesToActiveSplit(config.minutes);
            });
            extendButtonsContainer.appendChild(button);
        });

        overlay.appendChild(warningMessage);
        overlay.appendChild(splitMessage);
        overlay.appendChild(extendButtonsContainer);

        document.body.appendChild(overlay);
        console.log("YouTube Split: Оверлей показан.");
    }

    function getMinuteEnding(count) {
        const lastDigit = count % 10;
        const lastTwoDigits = count % 100;

        if (lastTwoDigits >= 11 && lastTwoDigits <= 14) {
            return ''; // минут
        }
        if (lastDigit === 1) {
            return 'а'; // минута
        }
        if (lastDigit >= 2 && lastDigit <= 4) {
            return 'ы'; // минуты
        }
        return ''; // минут
    }

    function removeOverlay() {
        if (overlay) {
            overlay.remove();
            overlay = null;
            if (audioPlayer) {
                 audioPlayer.pause();
                 audioPlayer.currentTime = 0;
            }
             console.log("YouTube Split: Оверлей убран.");
        }
    }

     // Инициализация аудио плеера и установка громкости
     function initAudioPlayer() {
         if (splitSoundUrl && splitSoundUrl !== 'ВАША_ПРЯМАЯ_ССЫЛКА_НА_ЗВУКОВОЙ_ФАЙЛ_ТУТ') {
             if (!audioPlayer || audioPlayer.src !== splitSoundUrl) {
                  if (audioPlayer) {
                     audioPlayer.pause();
                     audioPlayer = null;
                  }
                 audioPlayer = new Audio(splitSoundUrl);
                 audioPlayer.preload = 'auto';
                 audioPlayer.onerror = (e) => console.error("YouTube Split: Не удалось загрузить или воспроизвести звук:", e);
                 console.log("YouTube Split: Аудио плеер инициализирован.");

                 // Применяем сохраненную громкость после создания плеера
                 let savedVolume = localStorage.getItem(localStorageVolumeKey);
                 if (savedVolume !== null) {
                      audioPlayer.volume = parseFloat(savedVolume);
                 } else {
                      audioPlayer.volume = 0.5; // Громкость по умолчанию
                 }
             }
         } else {
             console.warn("YouTube Split: URL звука для сплита не указан или является плейсхолдером.");
              if (audioPlayer) {
                 audioPlayer.pause();
                 audioPlayer = null;
             }
         }
     }

    // --- Главная функция, вызываемая по интервалу для поиска элементов и настройки ---
    function setupElementsAndPanel() {
        // Внедряем стили и инициализируем аудио плеер при каждой попытке,
        // функции сами проверят, нужно ли это делать.
        injectStyles();
        initAudioPlayer();

        // Пытаемся найти видео и контейнер для панели
        video = document.querySelector("video"); // Обновляем ссылку на видео
        const primaryContainer = document.querySelector("ytd-watch-flexy #primary");
        const panel = document.getElementById("split-control-panel");


        if (video && primaryContainer) {
             // Элементы найдены
             if (!panelAdded) { // Проверяем наш флаг
                 // Если панель еще не добавлена
                 console.log("YouTube Split: Видео и контейнер #primary найдены. Добавляем панель.");
                 addControlPanel(primaryContainer); // Передаем найденный контейнер
             } else {
                  // Если панель уже добавлена, убедимся, что она на своем месте
                  ensurePanelPosition();
                  // updateSplitDisplay() НЕ вызывается здесь, чтобы не сбрасывать поле ввода
             }

        } else {
            // Элементы еще не найдены
             // Если панель была добавлена, но контейнер исчез (например, навигация),
             // удалим панель и сбросим флаг.
            if (panelAdded) {
                 console.log("YouTube Split: Необходимые элементы отсутствуют, удаляем панель и сбрасываем состояние.");
                 const existingPanel = document.getElementById("split-control-panel");
                 if(existingPanel) existingPanel.remove();
                 panelAdded = false; // Сбрасываем флаг
                 // Не сбрасываем splitMinutes на null здесь
                 // splitCheckIntervalId и overlay очищаются в urlObserver или checkSplitCondition
            }
             video = null; // Сбрасываем видео, если оно пропало
        }
    }

    // --- Запуск скрипта ---

    // Запускаем интервал для поиска элементов и настройки.
    // Этот интервал будет работать постоянно на страницах *://www.youtube.com/watch*
    // и будет пытаться добавить панель и найти видео/контейнер каждые 500мс.
    if (!setupIntervalId) {
        setupIntervalId = setInterval(setupElementsAndPanel, 500); // Проверяем каждые 500мс
         console.log("YouTube Split: Запущен основной интервал поиска элементов.");
    }


    // Очистка при уходе со страницы или навигации по SPA
    let lastUrl = location.href;
    const urlObserver = new MutationObserver(() => {
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            console.log("YouTube Split: URL changed, cleaning up and re-initializing.");

            // Очищаем предыдущие интервалы
            stopSplitCheckInterval(); // Останавливаем проверку сплита
            if (setupIntervalId) { // Останавливаем основной интервал поиска элементов
                 clearInterval(setupIntervalId);
                 setupIntervalId = null;
            }

            // Останавливаем звук и убираем оверлей
             if (audioPlayer) {
                 audioPlayer.pause();
             }
             removeOverlay();

             // Удаляем старую панель, если она есть
            const oldPanel = document.getElementById("split-control-panel");
            if (oldPanel) {
                oldPanel.remove();
            }
             // Удаляем старые стили, чтобы обновились, если нужно
            const oldStyles = document.getElementById("yt-split-styles");
             if(oldStyles) oldStyles.remove();

            // Сбрасываем глобальные переменные состояния
            splitMinutes = null; // Сплит не активен по умолчанию на новой странице
            video = null; // Видео будет найдено заново в setupElementsAndPanel
            splitTriggered = false;
            panelAdded = false; // Сбрасываем флаг добавления панели

            // Перезапускаем основной интервал для нового видео
            if (!setupIntervalId) {
                 setupIntervalId = setInterval(setupElementsAndPanel, 500);
                 console.log("YouTube Split: Перезапущен основной интервал поиска элементов после смены URL.");
            }
             // initAudioPlayer будет вызван в setupElementsAndPanel
        }
    });

    // Наблюдаем за изменениями в body для отслеживания SPA навигации.
     urlObserver.observe(document.body, {
        childList: true,
        subtree: true
     });


    // Очистка при закрытии вкладки
    window.addEventListener('beforeunload', function() {
         console.log("YouTube Split: Очистка при выгрузке страницы.");
        stopSplitCheckInterval();
        if (setupIntervalId) {
            clearInterval(setupIntervalId);
            setupIntervalId = null;
        }
         if (audioPlayer) {
            audioPlayer.pause();
            audioPlayer = null; // Удаляем объект Audio при окончательной выгрузке
         }
        if (urlObserver) {
             urlObserver.disconnect();
        }
    });


})();