// ==UserScript== // @name YouTube Split // @namespace http://tampermonkey.net/ // @version 2.6 // @author Gemini // @match *://www.youtube.com/watch* // @grant none // @description Устанавливает сплит по времени, панель управления под видео, показывает статистику "Выкуплено/Всего", оверлей "СПЛИТ НЕ ОПЛАЧЕН" с настраиваемым таймером и кнопками на оверлее, гифкой и звуком. // @downloadURL none // ==/UserScript== (function() { 'use strict'; // --- Конфигурация --- let splitMinutes = null; let totalVideoMinutes = null; const extendCost = 300; const splitSoundUrl = 'https://github.com/lardan099/donat/raw/refs/heads/main/alert_orig.mp3'; // ВСТАВЬТЕ СЮДА ПРЯМУЮ ССЫЛКУ НА ВАШ ЗВУК! const overlayGifUrl = 'https://i.imgur.com/SS5Nfff.gif'; // URL гифки для оверлея const localStorageVolumeKey = 'ytSplitAlertVolume'; const localStorageTimerKey = 'ytSplitOverlayTimer'; // Ключ для сохранения таймера в localStorage const defaultOverlayTimerDuration = 360; // Длительность таймера по умолчанию в секундах (6 минут) // --- Глобальные переменные состояния --- let overlayTimerDuration = parseInt(localStorage.getItem(localStorageTimerKey), 10); if (isNaN(overlayTimerDuration) || overlayTimerDuration < 0) { overlayTimerDuration = defaultOverlayTimerDuration; } let video = null; let overlay = null; let splitTriggered = false; let audioPlayer = null; let splitCheckIntervalId = null; let setupIntervalId = null; let panelAdded = false; let overlayTimerIntervalId = null; let overlayCountdownRemaining = overlayTimerDuration; 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; } #split-volume-control input[type="range"] { flex-grow: 1; min-width: 80px; -webkit-appearance: none; 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; } #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%; } #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-stats { font-size: 15px; color: var(--yt-spec-text-primary); font-weight: 500; margin-left: 10px; } #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: 20px; color: red; text-shadow: 0 0 15px rgba(255, 0, 0, 0.7); } /* Стили для таймера и сообщения "ОСТАЛОСЬ" на оверлее */ #split-overlay #overlay-timer, #split-overlay #overlay-remaining-minutes { font-size: clamp(28px, 5vw, 48px); font-weight: bold; margin-bottom: 20px; } #split-overlay #overlay-timer { color: orange; } #split-overlay #overlay-remaining-minutes { color: cyan; } /* Контейнер для управления таймером на оверлее */ #split-overlay #overlay-timer-control { margin-bottom: 20px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: center; color: white; /* Используем белый цвет для контраста */ font-size: 18px; } #split-overlay #overlay-timer-control label { font-weight: 500; } /* Стили для поля ввода таймера на оверлее */ #split-overlay #overlay-timer-control input[type="number"] { width: 70px; padding: 8px 10px; background: rgba(255, 255, 255, 0.1); color: white; border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 4px; text-align: center; font-size: 18px; -moz-appearance: textfield; } #split-overlay #overlay-timer-control input[type="number"]::-webkit-outer-spin-button, #split-overlay #overlay-timer-control input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } /* Стили для кнопок модификации таймера на оверлее */ #split-overlay #overlay-timer-control button { padding: 8px 12px; font-size: 16px; cursor: pointer; background: rgba(255, 255, 255, 0.1); color: white; border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 4px; transition: background 0.2s ease-in-out; font-weight: 500; } #split-overlay #overlay-timer-control button:hover { background: rgba(255, 255, 255, 0.2); } #split-extend-buttons { display: flex; gap: 15px; flex-wrap: wrap; justify-content: center; margin-bottom: 40px; } #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); } #split-overlay img { max-width: 115%; max-height: 50vh; height: auto; border-radius: 8px; margin-top: 20px; } `; 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); } function updateSplitDisplay() { const inputField = document.getElementById("split-input"); if (inputField) { inputField.valueAsNumber = splitMinutes === null ? 0 : splitMinutes; } updateSplitStatsDisplay(); } function updateSplitStatsDisplay() { const statsElement = document.getElementById("split-stats"); if (statsElement) { const boughtMinutes = splitMinutes === null ? 0 : splitMinutes; const totalMinutesText = totalVideoMinutes !== null ? `${totalVideoMinutes}` : '?'; statsElement.textContent = `Выкуплено: ${boughtMinutes} / Всего: ${totalVideoMinutes !== null ? totalVideoMinutes : '?'} минут`; } if (overlay) { updateOverlayRemainingMinutes(); } } // Функция для изменения значения в поле ввода сплита (минуты) на ПАНЕЛИ 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 modifyTimerInputOverlay(secondsToModify) { const inputField = document.getElementById("overlay-timer-input"); // Ищем по ID на оверлее if (!inputField) return; let currentVal = inputField.valueAsNumber; if (isNaN(currentVal)) currentVal = 0; let newVal = currentVal + secondsToModify; if (newVal < 0) newVal = 0; inputField.valueAsNumber = newVal; // Обновляем глобальную переменную и localStorage overlayTimerDuration = newVal; localStorage.setItem(localStorageTimerKey, overlayTimerDuration.toString()); // --- НАЧАЛО ИСПРАВЛЕНИЯ --- // При изменении длительности таймера кнопками, // сбрасываем текущий отсчет и перезапускаем таймер overlayCountdownRemaining = overlayTimerDuration; if (overlayCountdownRemaining < 0) overlayCountdownRemaining = 0; // Доп. проверка if (overlayTimerIntervalId) { clearInterval(overlayTimerIntervalId); // Останавливаем старый интервал overlayTimerIntervalId = null; // Сбрасываем ID } updateOverlayTimer(); // Обновляем отображение таймера немедленно if (overlayTimerDuration > 0) { // Запускаем новый интервал, если длительность > 0 overlayTimerIntervalId = setInterval(updateOverlayTimer, 1000); } // --- КОНЕЦ ИСПРАВЛЕНИЯ --- } function startSplitCheckInterval() { if (!splitCheckIntervalId) { splitCheckIntervalId = setInterval(checkSplitCondition, 500); } } function stopSplitCheckInterval() { if (splitCheckIntervalId) { clearInterval(splitCheckIntervalId); splitCheckIntervalId = null; } } function addControlPanel(primaryContainer) { if (panelAdded) { ensurePanelPosition(); updateSplitDisplay(); // Обновляем поле ввода сплита и статистику // Поле ввода таймера теперь только на оверлее return; } if (!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; if (splitMinutes > 0) { startSplitCheckInterval(); setButton.textContent = "СПЛИТ НАЧАТ"; setButton.style.background = 'var(--yt-spec-call-to-action)'; 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(); if (video.paused && oldSplitMinutes === null) { video.play(); } } } } else { // splitMinutes === 0 stopSplitCheckInterval(); splitTriggered = false; removeOverlay(); setButton.textContent = "НАЧАТЬ СПЛИТ"; setButton.style.background = 'var(--yt-spec-brand-suggested-action)'; if (video && video.paused && oldSplitMinutes !== null && oldSplitMinutes > 0) { video.play(); } } updateSplitDisplay(); updateSplitStatsDisplay(); } else { alert("Введите корректное число минут."); } }); // --- Группа управления сплитом (минуты) --- const splitLabel = document.createElement("label"); splitLabel.setAttribute("for", "split-input"); const labelTextMain = document.createTextNode("Сплит (мин):"); const breakElement = document.createElement("br"); const italicElement = document.createElement("i"); const labelTextInstruction = document.createTextNode("(уст. перед \"Начать\")"); splitLabel.appendChild(labelTextMain); splitLabel.appendChild(breakElement); italicElement.appendChild(labelTextInstruction); splitLabel.appendChild(italicElement); const splitInputGroup = document.createElement("div"); splitInputGroup.id = "split-input-group"; splitInputGroup.classList.add("split-input-group"); const splitInputField = document.createElement("input"); splitInputField.type = "number"; splitInputField.id = "split-input"; splitInputField.min = "0"; splitInputField.valueAsNumber = splitMinutes === null ? 0 : splitMinutes; const splitModifyButtons = [ { text: '+1', minutes: 1 }, { text: '+5', minutes: 5 }, { text: '+10', minutes: 10 }, { text: '+20', minutes: 20 } ]; splitModifyButtons.forEach(btnInfo => { const button = document.createElement("button"); button.textContent = btnInfo.text; // Используем modifySplitInput, которая работает с элементами на панели button.addEventListener("click", () => modifySplitInput(btnInfo.minutes)); splitInputGroup.appendChild(button); }); splitInputGroup.insertBefore(splitInputField, splitInputGroup.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"; volumeSlider.step = "0.05"; let savedVolume = localStorage.getItem(localStorageVolumeKey); if (savedVolume === null) { savedVolume = '0.5'; } volumeSlider.value = savedVolume; volumeSlider.addEventListener("input", function() { if (audioPlayer) { audioPlayer.volume = parseFloat(this.value); } localStorage.setItem(localStorageVolumeKey, this.value); }); volumeControlGroup.appendChild(volumeLabel); volumeControlGroup.appendChild(volumeSlider); if (audioPlayer) { audioPlayer.volume = parseFloat(savedVolume); } const statsElement = document.createElement("span"); statsElement.id = "split-stats"; // --- Собираем панель --- panel.appendChild(setButton); panel.appendChild(splitLabel); panel.appendChild(splitInputGroup); panel.appendChild(volumeControlGroup); panel.appendChild(statsElement); primaryContainer.insertBefore(panel, primaryContainer.firstChild); panelAdded = true; updateSplitDisplay(); updateSplitStatsDisplay(); } 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) { primaryContainer.insertBefore(panel, primaryContainer.firstChild); } } } function addMinutesToActiveSplit(minutesToAdd) { if (splitMinutes === null) return; splitMinutes += minutesToAdd; updateSplitDisplay(); if (overlay) { updateOverlayRemainingMinutes(); } const thresholdSeconds = splitMinutes * 60; if (video && video.currentTime < thresholdSeconds) { removeOverlay(); splitTriggered = false; video.play(); } } // Обновляет текст таймера на оверлее function updateOverlayTimer() { const timerElement = document.getElementById('overlay-timer'); if (timerElement) { if (overlayCountdownRemaining > 0) { const minutes = Math.floor(overlayCountdownRemaining / 60); const seconds = overlayCountdownRemaining % 60; const secondsFormatted = seconds < 10 ? '0' + seconds : seconds; timerElement.textContent = `ЖДЕМ ${minutes}:${secondsFormatted}, ИНАЧЕ СКИП`; overlayCountdownRemaining--; } else { timerElement.textContent = `ЖДЕМ 0:00, ИНАЧЕ СКИП`; if (overlayTimerIntervalId) { clearInterval(overlayTimerIntervalId); overlayTimerIntervalId = null; } // Здесь можно добавить логику автоматического скипа или действия по истечении таймера // Например: if (video) video.currentTime = video.duration; // Скип до конца } } else { if (overlayTimerIntervalId) { clearInterval(overlayTimerIntervalId); overlayTimerIntervalId = null; } } } // Обновляет сообщение "ОСТАЛОСЬ" на оверлее function updateOverlayRemainingMinutes() { const remainingElement = document.getElementById('overlay-remaining-minutes'); if (remainingElement) { const remainingMinutes = totalVideoMinutes !== null && splitMinutes !== null ? Math.max(0, totalVideoMinutes - splitMinutes) : '?'; if (totalVideoMinutes === null) { remainingElement.textContent = `ОСТАЛОСЬ ? минут выкупить`; } else { remainingElement.textContent = `ОСТАЛОСЬ ${remainingMinutes} минут выкупить`; } } } function checkSplitCondition() { if (!video) { video = document.querySelector("video"); if (!video) { stopSplitCheckInterval(); splitTriggered = false; removeOverlay(); return; } initAudioPlayer(); const volumeSlider = document.getElementById('split-volume-slider'); if(audioPlayer && volumeSlider) audioPlayer.volume = parseFloat(volumeSlider.value); } if (totalVideoMinutes === null && isFinite(video.duration) && video.duration > 0) { totalVideoMinutes = Math.ceil(video.duration / 60); if (panelAdded) { updateSplitStatsDisplay(); } } 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 { stopSplitCheckInterval(); splitTriggered = false; removeOverlay(); if (video && video.paused) video.play(); } } 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 timerElement = document.createElement("div"); timerElement.id = "overlay-timer"; // --- Элемент сообщения "ОСТАЛОСЬ" --- const remainingMinutesElement = document.createElement("div"); remainingMinutesElement.id = "overlay-remaining-minutes"; // --- Контейнер управления таймером на оверлее --- const overlayTimerControlGroup = document.createElement("div"); overlayTimerControlGroup.id = "overlay-timer-control"; const timerLabel = document.createElement("label"); timerLabel.setAttribute("for", "overlay-timer-input"); timerLabel.textContent = "Таймер (сек):"; const timerInputField = document.createElement("input"); timerInputField.type = "number"; timerInputField.id = "overlay-timer-input"; timerInputField.min = "0"; timerInputField.valueAsNumber = overlayTimerDuration; // Обработчик изменения поля ввода таймера (для сохранения значения) НА ОВЕРЛЕЕ timerInputField.addEventListener('input', function() { const val = this.valueAsNumber; if (!isNaN(val) && val >= 0) { overlayTimerDuration = val; localStorage.setItem(localStorageTimerKey, overlayTimerDuration.toString()); // При изменении длительности таймера, сбрасываем текущий отсчет и перезапускаем таймер overlayCountdownRemaining = overlayTimerDuration; if (overlayTimerIntervalId) clearInterval(overlayTimerIntervalId); if (overlayTimerDuration > 0) { overlayTimerIntervalId = setInterval(updateOverlayTimer, 1000); } else { overlayTimerIntervalId = null; } updateOverlayTimer(); // Обновить отображение сразу } else { this.valueAsNumber = overlayTimerDuration; } }); const timerModifyButtons = [ { text: '-60', seconds: -60 }, // Уменьшение/увеличение на минуту { text: '-10', seconds: -10 }, { text: '-5', seconds: -5 }, { text: '+5', seconds: 5 }, { text: '+10', seconds: 10 }, { text: '+60', seconds: 60 } // Уменьшение/увеличение на минуту ]; timerModifyButtons.forEach(btnInfo => { const button = document.createElement("button"); button.textContent = btnInfo.text; // Вызываем modifyTimerInputOverlay, которая работает с элементами на оверлее button.addEventListener("click", () => modifyTimerInputOverlay(btnInfo.seconds)); overlayTimerControlGroup.appendChild(button); }); // Вставляем поле ввода первым в группу таймера на оверлее overlayTimerControlGroup.insertBefore(timerInputField, overlayTimerControlGroup.children[0]); // --- Контейнер кнопок продления --- 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); }); // --- Добавление гифки --- const gifElement = document.createElement("img"); gifElement.src = overlayGifUrl; overlay.appendChild(warningMessage); overlay.appendChild(splitMessage); overlay.appendChild(timerElement); overlay.appendChild(remainingMinutesElement); overlay.appendChild(overlayTimerControlGroup); // Добавляем контейнер управления таймером оверлея overlay.appendChild(extendButtonsContainer); overlay.appendChild(gifElement); document.body.appendChild(overlay); // --- Запуск таймера и обновление сообщения "Осталось" при показе оверлея --- overlayCountdownRemaining = overlayTimerDuration; // Инициализируем отсчет установленной длительностью if (overlayCountdownRemaining < 0) overlayCountdownRemaining = 0; updateOverlayTimer(); updateOverlayRemainingMinutes(); if (overlayTimerDuration > 0 && !overlayTimerIntervalId) { overlayTimerIntervalId = setInterval(updateOverlayTimer, 1000); } } 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 (overlayTimerIntervalId) { clearInterval(overlayTimerIntervalId); overlayTimerIntervalId = null; } if (audioPlayer) { audioPlayer.pause(); audioPlayer.currentTime = 0; } } } 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); let savedVolume = localStorage.getItem(localStorageVolumeKey); if (savedVolume !== null) { audioPlayer.volume = parseFloat(savedVolume); } else { audioPlayer.volume = 0.5; } } } else { 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) { addControlPanel(primaryContainer); } else { ensurePanelPosition(); } if (totalVideoMinutes === null && isFinite(video.duration) && video.duration > 0) { totalVideoMinutes = Math.ceil(video.duration / 60); if (panelAdded) { updateSplitStatsDisplay(); } } } else { if (panelAdded) { const existingPanel = document.getElementById("split-control-panel"); if(existingPanel) existingPanel.remove(); panelAdded = false; } video = null; totalVideoMinutes = null; } } if (!setupIntervalId) { setupIntervalId = setInterval(setupElementsAndPanel, 500); } let lastUrl = location.href; const urlObserver = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; stopSplitCheckInterval(); if (overlayTimerIntervalId) { clearInterval(overlayTimerIntervalId); overlayTimerIntervalId = null; } 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; totalVideoMinutes = null; video = null; splitTriggered = false; panelAdded = false; // overlayTimerDuration сохраняется в localStorage if (!setupIntervalId) { setupIntervalId = setInterval(setupElementsAndPanel, 500); } } }); urlObserver.observe(document.body, { childList: true, subtree: true }); window.addEventListener('beforeunload', function() { stopSplitCheckInterval(); if (overlayTimerIntervalId) { clearInterval(overlayTimerIntervalId); overlayTimerIntervalId = null; } if (setupIntervalId) { clearInterval(setupIntervalId); setupIntervalId = null; } if (audioPlayer) { audioPlayer.pause(); audioPlayer = null; } if (urlObserver) { urlObserver.disconnect(); } }); })();