привет. скажи, почему у меня при нажатии на кнопку скачать сегменты в бэке начинается загрузка ещё и с 0 сегмента? то есть те, которые надо, грузятся, но в каком-то потоке ещё и с 0 грузятся. как это отключить, убрать? // ==UserScript==
// @name         HLS Video Downloader
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Загрузка фрагментов HLS (m3u8) видео в формате MP4
// @author       You
// @match        *://recurbate2.com/*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @require      https://cdn.jsdelivr.net/npm/hls.js@latest
// @require      https://cdn.jsdelivr.net/npm/mp4box@0.5.2/dist/mp4box.all.min.js
// ==/UserScript==

(function() {
    'use strict';

    // Стили для интерфейса
    GM_addStyle(`
        #hls-downloader-container {
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 350px;
            background-color: #fff;
            border: 1px solid #ccc;
            border-radius: 5px;
            padding: 10px;
            z-index: 9999;
            box-shadow: 0 0 10px rgba(0,0,0,0.2);
            font-family: Arial, sans-serif;
            max-height: 500px;
            overflow-y: auto;
        }
        #hls-downloader-container h3 {
            margin-top: 0;
            margin-bottom: 10px;
            font-size: 16px;
        }
        #hls-downloader-container select,
        #hls-downloader-container input {
            width: 100%;
            margin-bottom: 10px;
            padding: 5px;
        }
        #hls-downloader-container button {
            background-color: #4CAF50;
            color: white;
            border: none;
            padding: 8px 12px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 14px;
            margin: 4px 2px;
            cursor: pointer;
            border-radius: 4px;
        }
        #hls-downloader-container .progress {
            width: 100%;
            background-color: #f3f3f3;
            border-radius: 5px;
            margin-top: 10px;
        }
        #hls-downloader-container .progress-bar {
            height: 20px;
            background-color: #4CAF50;
            border-radius: 5px;
            width: 0%;
            text-align: center;
            line-height: 20px;
            color: white;
        }
        .hls-toggle-btn {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 50%;
            width: 50px;
            height: 50px;
            font-size: 24px;
            cursor: pointer;
            z-index: 9998;
            display: flex;
            align-items: center;
            justify-content: center;
        }
    `);

    // Обнаруженные m3u8 ссылки
    let hlsLinks = [];

    // Создаем кнопку для открытия/закрытия интерфейса
    const toggleBtn = document.createElement('button');
    toggleBtn.className = 'hls-toggle-btn';
    toggleBtn.textContent = '+';
    toggleBtn.title = 'HLS Downloader';
    document.body.appendChild(toggleBtn);

    // Создаем контейнер для интерфейса
    const container = document.createElement('div');
    container.id = 'hls-downloader-container';
    container.style.display = 'none';
    container.innerHTML = `
        <h3>HLS Video Downloader</h3>
        <div>
            <select id="hls-url-select">
                <option value="">Выберите m3u8 поток</option>
            </select>
            <div>
                <label for="start-time">Начало (секунды):</label>
                <input type="number" id="start-time" min="0" value="0">
            </div>
            <div>
                <label for="end-time">Конец (секунды):</label>
                <input type="number" id="end-time" min="0" value="60">
            </div>
            <div>
                <label for="parallel-streams">Количество потоков загрузки:</label>
                <input type="number" id="parallel-streams" min="1" max="10" value="3">
            </div>
            <div>
                <label for="retry-count">Количество повторных попыток:</label>
                <input type="number" id="retry-count" min="1" max="10" value="3">
            </div>
            <button id="download-btn">Скачать фрагмент</button>
            <div class="progress">
                <div class="progress-bar" id="download-progress">0%</div>
            </div>
        </div>
    `;
    document.body.appendChild(container);

    // Обработчик для кнопки открытия/закрытия
    toggleBtn.addEventListener('click', function() {
        if (container.style.display === 'none') {
            container.style.display = 'block';
            toggleBtn.textContent = '-';
            // Обновляем список m3u8 ссылок при открытии
            scanForHlsLinks();
        } else {
            container.style.display = 'none';
            toggleBtn.textContent = '+';
        }
    });

    // Функция для сканирования страницы на наличие m3u8 ссылок
    function scanForHlsLinks() {
        hlsLinks = [];
        const urlSelect = document.getElementById('hls-url-select');

        // Очищаем текущий список
        while (urlSelect.options.length > 1) {
            urlSelect.remove(1);
        }

        // Ищем в сетевых запросах
        if (window.performance && window.performance.getEntries) {
            const entries = window.performance.getEntries();
            for (const entry of entries) {
                if (entry.name && typeof entry.name === 'string' &&
                    (entry.name.includes('.m3u8') || entry.name.includes('m3u8?'))) {
                    if (!hlsLinks.includes(entry.name)) {
                        hlsLinks.push(entry.name);
                        const option = document.createElement('option');
                        option.value = entry.name;
                        option.textContent = entry.name.substring(0, 50) + '...';
                        urlSelect.appendChild(option);
                    }
                }
            }
        }

        // Ищем в DOM
        const allElements = document.querySelectorAll('*');
        for (const el of allElements) {
            // Проверяем атрибуты src и href
            for (const attr of ['src', 'href', 'data-src']) {
                const value = el.getAttribute(attr);
                if (value && typeof value === 'string' &&
                    (value.includes('.m3u8') || value.includes('m3u8?'))) {
                    if (!hlsLinks.includes(value)) {
                        hlsLinks.push(value);
                        const option = document.createElement('option');
                        option.value = value;
                        option.textContent = value.substring(0, 50) + '...';
                        urlSelect.appendChild(option);
                    }
                }
            }
        }

        // Если не нашли ни одной ссылки, добавляем возможность ввести вручную
        if (hlsLinks.length === 0) {
            const option = document.createElement('option');
            option.value = 'manual';
            option.textContent = 'Ввести URL вручную';
            urlSelect.appendChild(option);
        }
    }

    // Обработчик изменения выбора m3u8 потока
    document.getElementById('hls-url-select').addEventListener('change', function() {
        if (this.value === 'manual') {
            const manualUrl = prompt('Введите URL m3u8 потока:');
            if (manualUrl && (manualUrl.includes('.m3u8') || manualUrl.includes('m3u8?'))) {
                hlsLinks.push(manualUrl);
                const option = document.createElement('option');
                option.value = manualUrl;
                option.textContent = manualUrl.substring(0, 50) + '...';
                this.appendChild(option);
                this.value = manualUrl;
            } else {
                this.value = '';
                alert('Неверный URL. Пожалуйста, введите корректный m3u8 URL.');
            }
        }
    });

    // Обработчик кнопки скачивания
    document.getElementById('download-btn').addEventListener('click', function() {
        const selectedUrl = document.getElementById('hls-url-select').value;
        const startTime = parseFloat(document.getElementById('start-time').value) || 0;
        const endTime = parseFloat(document.getElementById('end-time').value) || 60;
        const parallelStreams = parseInt(document.getElementById('parallel-streams').value) || 3;
        const maxRetries = parseInt(document.getElementById('retry-count').value) || 3;

        if (!selectedUrl) {
            alert('Пожалуйста, выберите m3u8 поток');
            return;
        }

        if (endTime <= startTime) {
            alert('Время окончания должно быть больше времени начала');
            return;
        }

        downloadHlsSegment(selectedUrl, startTime, endTime, parallelStreams, maxRetries);
    });

    // Функция для загрузки сегментов HLS напрямую
    function downloadHlsSegment(url, startTime, endTime, parallelStreams, maxRetries) {
        const progressBar = document.getElementById('download-progress');
        progressBar.style.width = '0%';
        progressBar.textContent = 'Инициализация...';

        if (!Hls.isSupported()) {
            alert('Ваш браузер не поддерживает HLS.js');
            return;
        }

        // Создаем видео элемент без звука
        const video = document.createElement('video');
        video.style.display = 'none';
        video.muted = true; // Отключаем звук
        document.body.appendChild(video);

        // Настраиваем HLS.js с параллельной загрузкой
        const hlsConfig = {
            maxBufferLength: 30,
            maxMaxBufferLength: 600,
            maxBufferSize: 60 * 1000 * 1000, // 60MB
            maxBufferHole: 0.5,
            lowLatencyMode: false,
            fragLoadingMaxRetry: maxRetries,
            manifestLoadingMaxRetry: maxRetries,
            levelLoadingMaxRetry: maxRetries,
            fragLoadingMaxRetryTimeout: 10000,
            manifestLoadingMaxRetryTimeout: 10000,
            levelLoadingMaxRetryTimeout: 10000,
            startFragPrefetch: true,
            maxParallelLoads: parallelStreams,
            abrEwmaFastLive: 3.0,
            abrEwmaSlowLive: 9.0,
            abrEwmaFastVoD: 3.0,
            abrEwmaSlowVoD: 9.0,
            backBufferLength: 30,
            autoStartLoad: false,
            startPosition: startTime
        };

        const hls = new Hls(hlsConfig);
        hls.loadSource(url);
        hls.attachMedia(video);

        // Получаем название страницы для имени файла
        let pageTitle = document.title || 'video';
        // Очищаем название от недопустимых символов
        pageTitle = pageTitle.replace(/[\\/:*?"<>|]/g, '_').trim();
        if (!pageTitle) pageTitle = 'video';

        // Переменные для отслеживания загрузки
        let segmentsLoaded = 0;
        let totalSegments = 0;
        let segmentDuration = 0;
        let startSegmentIndex = 0;
        let endSegmentIndex = 0;
        let segmentData = [];
        let isDownloading = false;

        // Переменные для управления скоростью запросов
        const rateLimitDelay = 100; // Минимальная задержка между запросами в мс
        let lastRequestTime = 0;
        let retryDelays = {}; // Хранит задержки для повторных попыток по URL

        // Обработчик события загрузки манифеста
        hls.on(Hls.Events.MANIFEST_PARSED, function(event, data) {
            progressBar.textContent = 'Анализ манифеста...';
            progressBar.style.width = '5%';

            // Получаем информацию о сегментах
            if (data.levels && data.levels.length > 0) {
                const level = data.levels[0];
                if (level.details && level.details.fragments && level.details.fragments.length > 0) {
                    const fragments = level.details.fragments;
                    segmentDuration = fragments[0].duration;

                    // Определяем индексы начального и конечного сегментов
                    for (let i = 0; i < fragments.length; i++) {
                        const fragment = fragments[i];
                        if (fragment.start <= startTime && fragment.start + fragment.duration > startTime) {
                            startSegmentIndex = i;
                        }
                        if (fragment.start <= endTime && fragment.start + fragment.duration > endTime) {
                            endSegmentIndex = i;
                            break;
                        }
                    }

                    // Если не удалось определить конечный индекс, берем последний сегмент
                    if (endSegmentIndex === 0 && fragments.length > 0) {
                        endSegmentIndex = fragments.length - 1;
                    }

                    totalSegments = endSegmentIndex - startSegmentIndex + 1;

                    // Инициализируем массив для хранения данных сегментов
                    segmentData = new Array(totalSegments);

                    // Начинаем загрузку сегментов
                    downloadSegments(fragments, startSegmentIndex, endSegmentIndex, parallelStreams, maxRetries);
                    isDownloading = true;
                }
            }
        });

        // Функция для параллельной загрузки сегментов с управлением скоростью и повторными попытками
        function downloadSegments(fragments, startIdx, endIdx, maxParallel, maxRetries) {
            progressBar.textContent = 'Загрузка сегментов...';
            progressBar.style.width = '10%';

            // Создаем очередь сегментов для загрузки
            const segmentsToDownload = [];
            for (let i = startIdx; i <= endIdx; i++) {
                segmentsToDownload.push({
                    index: i,
                    url: fragments[i].url,
                    arrayIndex: i - startIdx,
                    retries: 0
                });
            }

            // Функция для загрузки одного сегмента с повторными попытками
            function downloadSegment(segment) {
                return new Promise((resolve, reject) => {
                    // Функция для выполнения запроса с учетом ограничения скорости
                    const executeRequest = () => {
                        // Проверяем, не превышено ли количество попыток
                        if (segment.retries >= maxRetries) {
                            return reject(new Error(`Превышено количество попыток для сегмента ${segment.index}`));
                        }

                        // Увеличиваем счетчик попыток
                        segment.retries++;

                        // Вычисляем задержку для соблюдения ограничения скорости
                        const now = Date.now();
                        const timeSinceLastRequest = now - lastRequestTime;
                        const delay = Math.max(0, rateLimitDelay - timeSinceLastRequest);

                        // Добавляем дополнительную задержку для повторных попыток (экспоненциальное увеличение)
                        const retryDelay = segment.retries > 1 ? Math.min(30000, Math.pow(2, segment.retries - 1) * 1000) : 0;

                        // Применяем задержку перед отправкой запроса
                        setTimeout(() => {
                            lastRequestTime = Date.now();

                            const xhr = new XMLHttpRequest();
                            xhr.open('GET', segment.url, true);
                            xhr.responseType = 'arraybuffer';

                            xhr.onload = function() {
                                if (xhr.status >= 200 && xhr.status < 300) {
                                    // Успешная загрузка
                                    segmentData[segment.arrayIndex] = new Uint8Array(xhr.response);
                                    segmentsLoaded++;

                                    // Обновляем прогресс
                                    const progress = (segmentsLoaded / totalSegments) * 80 + 10;
                                    progressBar.style.width = `${progress}%`;
                                    progressBar.textContent = `Загружено ${segmentsLoaded} из ${totalSegments} сегментов (${Math.round(progress)}%)`;

                                    resolve();
                                } else if (xhr.status === 429) {
                                    // Too Many Requests - увеличиваем задержку и пробуем снова
                                    console.warn(`Получен 429 для сегмента ${segment.index}, повторная попытка через ${retryDelay + 5000}ms`);

                                    // Увеличиваем задержку для всех последующих запросов
                                    setTimeout(() => {
                                        executeRequest();
                                    }, retryDelay + 5000); // Добавляем 5 секунд к стандартной задержке
                                } else {
                                    console.warn(`Ошибка ${xhr.status} для сегмента ${segment.index}, повторная попытка ${segment.retries}/${maxRetries}`);

                                    // Другие ошибки - пробуем снова с обычной задержкой
                                    setTimeout(() => {
                                        executeRequest();
                                    }, retryDelay);
                                }
                            };

                            xhr.onerror = function() {
                                console.error(`Сетевая ошибка для сегмента ${segment.index}, повторная попытка ${segment.retries}/${maxRetries}`);

                                // Сетевая ошибка - пробуем снова
                                setTimeout(() => {
                                    executeRequest();
                                }, retryDelay);
                            };

                            xhr.ontimeout = function() {
                                console.error(`Таймаут для сегмента ${segment.index}, повторная попытка ${segment.retries}/${maxRetries}`);

                                // Таймаут - пробуем снова
                                setTimeout(() => {
                                    executeRequest();
                                }, retryDelay);
                            };

                            // Устанавливаем таймаут для запроса
                            xhr.timeout = 30000; // 30 секунд

                            try {
                                xhr.send();
                            } catch (e) {
                                console.error(`Ошибка отправки запроса для сегмента ${segment.index}:`, e);

                                // Ошибка отправки - пробуем снова
                                setTimeout(() => {
                                    executeRequest();
                                }, retryDelay);
                            }
                        }, delay);
                    };

                    // Запускаем первый запрос
                    executeRequest();
                });
            }

            // Функция для параллельной загрузки с ограничением и очередью
            async function downloadWithLimit() {
                const activeDownloads = new Set();
                const queue = [...segmentsToDownload];

                // Функция для обработки следующего сегмента из очереди
                async function processNextSegment() {
                    if (queue.length === 0) return;

                    const segment = queue.shift();
                    const downloadPromise = downloadSegment(segment)
                        .catch(error => {
                            console.error(`Не удалось загрузить сегмент ${segment.index} после ${maxRetries} попыток:`, error);
                            // Помечаем сегмент как неудачный, но продолжаем загрузку других
                            progressBar.textContent = `Ошибка сегмента ${segment.index}, продолжаем загрузку...`;
                        })
                        .finally(() => {
                            activeDownloads.delete(downloadPromise);
                            // Запускаем загрузку следующего сегмента
                            processNextSegment();
                        });

                    activeDownloads.add(downloadPromise);
                }

                // Запускаем начальные загрузки (по количеству параллельных потоков)
                const initialDownloads = Math.min(maxParallel, queue.length);
                for (let i = 0; i < initialDownloads; i++) {
                    processNextSegment();
                }

                // Ждем завершения всех загрузок
                while (activeDownloads.size > 0) {
                    await Promise.race([...activeDownloads]);
                }

                // Проверяем, все ли сегменты загружены
                const missingSegments = segmentData.findIndex(segment => !(segment instanceof Uint8Array));
                if (missingSegments >= 0) {
                    console.warn(`Отсутствуют сегменты, начиная с индекса ${missingSegments}`);
                    progressBar.textContent = `Внимание: не все сегменты загружены (${segmentsLoaded} из ${totalSegments})`;
                }

                // Создаем MP4 из загруженных сегментов
                createMP4FromSegments();
            }

            // Запускаем параллельную загрузку
            downloadWithLimit().catch(err => {
                console.error('Ошибка при загрузке сегментов:', err);
                progressBar.textContent = 'Ошибка загрузки';
                alert('Произошла ошибка при загрузке сегментов: ' + err.message);
            });
        }

        // Функция для создания MP4 из загруженных сегментов
        function createMP4FromSegments() {
            progressBar.textContent = 'Создание MP4...';
            progressBar.style.width = '90%';

            // Проверяем, что хотя бы некоторые сегменты загружены
            const loadedSegments = segmentData.filter(segment => segment instanceof Uint8Array);

            if (loadedSegments.length === 0) {
                progressBar.textContent = 'Ошибка: не загружено ни одного сегмента';
                alert('Не удалось загрузить ни одного сегмента. Проверьте соединение и попробуйте еще раз.');
                return;
            }

            try {
                // Создаем MP4Box для обработки сегментов
                const mp4boxfile = MP4Box.createFile();

                // Обработчик для готового файла
                mp4boxfile.onReady = function(info) {
                    // Создаем буфер для финального файла
                    const finalBuffer = mp4boxfile.getBuffer();

                    // Создаем Blob и скачиваем файл
                    const blob = new Blob([finalBuffer], { type: 'video/mp4' });
                    const url = URL.createObjectURL(blob);

                    // Создаем имя файла на основе заголовка страницы и временного промежутка
                    const filename = `${pageTitle}_${startTime}_to_${endTime}.mp4`;

                    // Скачиваем файл
                    const a = document.createElement('a');
                    a.href = url;
                    a.download = filename;
                    a.style.display = 'none';
                    document.body.appendChild(a);
                    a.click();

                    setTimeout(function() {
                        document.body.removeChild(a);
                        URL.revokeObjectURL(url);
                        progressBar.textContent = 'Готово!';
                        progressBar.style.width = '100%';
                    }, 100);
                };

                // Обработчик ошибок MP4Box
                mp4boxfile.onError = function(e) {
                    console.error('Ошибка MP4Box:', e);
                    progressBar.textContent = 'Ошибка создания MP4';
                    alert('Произошла ошибка при создании MP4 файла: ' + e);
                };

                // Добавляем все загруженные сегменты в MP4Box
                for (let i = 0; i < segmentData.length; i++) {
                    if (segmentData[i] instanceof Uint8Array) {
                        mp4boxfile.appendBuffer(segmentData[i].buffer);
                    }
                }

                // Завершаем файл
                mp4boxfile.flush();
            } catch (e) {
                console.error('Ошибка при создании MP4:', e);
                progressBar.textContent = 'Ошибка создания MP4';
                alert('Произошла ошибка при создании MP4 файла: ' + e.message);

                // Альтернативный метод - просто объединяем сегменты в один файл
                try {
                    const loadedSegments = segmentData.filter(segment => segment instanceof Uint8Array);
                    const totalLength = loadedSegments.reduce((total, segment) => total + segment.length, 0);
                    const combinedArray = new Uint8Array(totalLength);

                    let offset = 0;
                    for (let i = 0; i < segmentData.length; i++) {
                        if (segmentData[i] instanceof Uint8Array) {
                            combinedArray.set(segmentData[i], offset);
                            offset += segmentData[i].length;
                        }
                    }

                    const blob = new Blob([combinedArray], { type: 'video/mp4' });
                    const url = URL.createObjectURL(blob);

                    // Создаем имя файла на основе заголовка страницы и временного промежутка
                    const filename = `${pageTitle}_${startTime}_to_${endTime}_raw.mp4`;

                    // Скачиваем файл
                    const a = document.createElement('a');
                    a.href = url;
                    a.download = filename;
                    a.style.display = 'none';
                    document.body.appendChild(a);
                    a.click();

                    setTimeout(function() {
                        document.body.removeChild(a);
                        URL.revokeObjectURL(url);
                        progressBar.textContent = 'Готово (сырой файл)!';
                        progressBar.style.width = '100%';
                    }, 100);
                } catch (e2) {
                    console.error('Ошибка при создании сырого файла:', e2);
                    progressBar.textContent = 'Критическая ошибка';
                    alert('Не удалось создать файл: ' + e2.message);
                }
            }
        }

        // Обработка ошибок HLS
        hls.on(Hls.Events.ERROR, function(event, data) {
            if (data.fatal) {
                switch(data.type) {
                    case Hls.ErrorTypes.NETWORK_ERROR:
                        progressBar.textContent = 'Ошибка сети';
                        alert('Ошибка сети при загрузке HLS потока. Попробуйте еще раз.');
                        hls.destroy();
                        document.body.removeChild(video);
                        break;
                    case Hls.ErrorTypes.MEDIA_ERROR:
                        progressBar.textContent = 'Ошибка медиа';
                        alert('Ошибка медиа при обработке HLS потока. Попробуйте еще раз.');
                        hls.destroy();
                        document.body.removeChild(video);
                        break;
                    default:
                        progressBar.textContent = 'Ошибка';
                        alert('Ошибка при обработке HLS потока. Попробуйте еще раз.');
                        hls.destroy();
                        document.body.removeChild(video);
                        break;
                }
            }
        });

        // Таймаут на случай, если манифест не загрузится
        setTimeout(function() {
            if (!isDownloading) {
                progressBar.textContent = 'Таймаут загрузки';
                alert('Не удалось загрузить манифест HLS. Проверьте URL и попробуйте еще раз.');
                hls.destroy();
                document.body.removeChild(video);
            }
        }, 15000);
    }

    // Запускаем сканирование при загрузке страницы
    setTimeout(scanForHlsLinks, 2000);

    // Периодически обновляем список ссылок
    setInterval(scanForHlsLinks, 30000);
})();
        // Функция для параллельной загрузки сегментов
        function downloadSegments(fragments, startIdx, endIdx, maxParallel) {
            progressBar.textContent = 'Загрузка сегментов...';
            progressBar.style.width = '10%';

            // Создаем очередь сегментов для загрузки
            const segmentsToDownload = [];
            for (let i = startIdx; i <= endIdx; i++) {
                segmentsToDownload.push({
                    index: i,
                    url: fragments[i].url,
                    arrayIndex: i - startIdx,
                    retries: 0,
                    maxRetries: 5 // Максимальное количество повторных попыток
                });
            }

            // Переменные для управления скоростью запросов
            const rateLimitDelay = 200; // Минимальная задержка между запросами в мс
            let lastRequestTime = 0;
            let backoffTime = 1000; // Начальное время отсрочки при 429 (1 секунда)
            let isRateLimited = false;

            // Функция для загрузки одного сегмента с повторными попытками
            function downloadSegment(segment) {
                return new Promise((resolve, reject) => {
                    const executeRequest = () => {
                        // Проверяем, не превышено ли количество попыток
                        if (segment.retries >= segment.maxRetries) {
                            return reject(new Error(`Превышено количество попыток для сегмента ${segment.index}`));
                        }

                        // Увеличиваем счетчик попыток
                        segment.retries++;

                        // Вычисляем задержку для соблюдения ограничения скорости
                        const now = Date.now();
                        const timeSinceLastRequest = now - lastRequestTime;
                        const delay = Math.max(0, rateLimitDelay - timeSinceLastRequest);

                        // Если активно ограничение скорости, добавляем дополнительную задержку
                        const effectiveDelay = isRateLimited ? delay + backoffTime : delay;

                        setTimeout(() => {
                            lastRequestTime = Date.now();

                            const xhr = new XMLHttpRequest();
                            xhr.open('GET', segment.url, true);
                            xhr.responseType = 'arraybuffer';

                            xhr.onload = function() {
                                if (xhr.status >= 200 && xhr.status < 300) {
                                    // Успешная загрузка
                                    segmentData[segment.arrayIndex] = new Uint8Array(xhr.response);
                                    segmentsLoaded++;

                                    // Сбрасываем флаг ограничения скорости после успешного запроса
                                    if (isRateLimited && segment.retries > 1) {
                                        isRateLimited = false;
                                        backoffTime = 1000; // Сбрасываем время отсрочки
                                    }

                                    // Обновляем прогресс
                                    const progress = (segmentsLoaded / totalSegments) * 80 + 10;
                                    progressBar.style.width = `${progress}%`;
                                    progressBar.textContent = `Загружено ${segmentsLoaded} из ${totalSegments} сегментов (${Math.round(progress)}%)`;

                                    resolve();
                                } else if (xhr.status === 429) {
                                    // Too Many Requests - увеличиваем задержку и пробуем снова
                                    console.warn(`Получен 429 для сегмента ${segment.index}, повторная попытка через ${backoffTime}ms`);

                                    // Устанавливаем флаг ограничения скорости
                                    isRateLimited = true;

                                    // Увеличиваем время отсрочки (экспоненциальное увеличение)
                                    backoffTime = Math.min(30000, backoffTime * 2);

                                    // Пробуем снова с увеличенной задержкой
                                    setTimeout(() => {
                                        executeRequest();
                                    }, backoffTime);
                                } else {
                                    console.warn(`Ошибка ${xhr.status} для сегмента ${segment.index}, повторная попытка ${segment.retries}/${segment.maxRetries}`);

                                    // Другие ошибки - пробуем снова с небольшой задержкой
                                    setTimeout(() => {
                                        executeRequest();
                                    }, 1000);
                                }
                            };

                            xhr.onerror = function() {
                                console.error(`Сетевая ошибка для сегмента ${segment.index}, повторная попытка ${segment.retries}/${segment.maxRetries}`);

                                // Сетевая ошибка - пробуем снова
                                setTimeout(() => {
                                    executeRequest();
                                }, 2000);
                            };

                            xhr.ontimeout = function() {
                                console.error(`Таймаут для сегмента ${segment.index}, повторная попытка ${segment.retries}/${segment.maxRetries}`);

                                // Таймаут - пробуем снова
                                setTimeout(() => {
                                    executeRequest();
                                }, 2000);
                            };

                            // Устанавливаем таймаут для запроса
                            xhr.timeout = 30000; // 30 секунд

                            try {
                                xhr.send();
                            } catch (e) {
                                console.error(`Ошибка отправки запроса для сегмента ${segment.index}:`, e);

                                // Ошибка отправки - пробуем снова
                                setTimeout(() => {
                                    executeRequest();
                                }, 2000);
                            }
                        }, effectiveDelay);
                    };

                    // Запускаем первый запрос
                    executeRequest();
                });
            }

            // Функция для параллельной загрузки с ограничением и очередью
            async function downloadWithLimit() {
                const activeDownloads = new Set();
                const queue = [...segmentsToDownload];

                // Функция для обработки следующего сегмента из очереди
                async function processNextSegment() {
                    if (queue.length === 0) return;

                    const segment = queue.shift();
                    const downloadPromise = downloadSegment(segment)
                        .catch(error => {
                            console.error(`Не удалось загрузить сегмент ${segment.index} после ${segment.maxRetries} попыток:`, error);
                            // Помечаем сегмент как неудачный, но продолжаем загрузку других
                            progressBar.textContent = `Ошибка сегмента ${segment.index}, продолжаем загрузку...`;
                        })
                        .finally(() => {
                            activeDownloads.delete(downloadPromise);
                            // Запускаем загрузку следующего сегмента
                            processNextSegment();
                        });

                    activeDownloads.add(downloadPromise);
                }

                // Запускаем начальные загрузки (по количеству параллельных потоков)
                const initialDownloads = Math.min(maxParallel, queue.length);
                for (let i = 0; i < initialDownloads; i++) {
                    processNextSegment();
                }

                // Ждем завершения всех загрузок
                while (activeDownloads.size > 0) {
                    await Promise.race([...activeDownloads]);
                }

                // Проверяем, все ли сегменты загружены
                const missingSegments = segmentData.findIndex(segment => !(segment instanceof Uint8Array));
                if (missingSegments >= 0) {
                    console.warn(`Отсутствуют сегменты, начиная с индекса ${missingSegments}`);
                    progressBar.textContent = `Внимание: не все сегменты загружены (${segmentsLoaded} из ${totalSegments})`;
                }

                // Создаем MP4 из загруженных сегментов
                createMP4FromSegments();
            }

            // Запускаем параллельную загрузку
            downloadWithLimit().catch(err => {
                console.error('Ошибка при загрузке сегментов:', err);
                progressBar.textContent = 'Ошибка загрузки';
                alert('Произошла ошибка при загрузке сегментов: ' + err.message);
            });
        }

        // Функция для создания MP4 из загруженных сегментов
        function createMP4FromSegments() {
            progressBar.textContent = 'Создание MP4...';
            progressBar.style.width = '90%';

            // Проверяем, что хотя бы некоторые сегменты загружены
            const loadedSegments = segmentData.filter(segment => segment instanceof Uint8Array);

            if (loadedSegments.length === 0) {
                progressBar.textContent = 'Ошибка: не загружено ни одного сегмента';
                alert('Не удалось загрузить ни одного сегмента. Проверьте соединение и попробуйте еще раз.');
                return;
            }

            try {
                // Создаем MP4Box для обработки сегментов
                const mp4boxfile = MP4Box.createFile();

                // Обработчик для готового файла
                mp4boxfile.onReady = function(info) {
                    // Создаем буфер для финального файла
                    const finalBuffer = mp4boxfile.getBuffer();

                    // Создаем Blob и скачиваем файл
                    const blob = new Blob([finalBuffer], { type: 'video/mp4' });
                    const url = URL.createObjectURL(blob);

                    // Создаем имя файла на основе заголовка страницы и временного промежутка
                    const filename = `${pageTitle}_${startTime}_to_${endTime}.mp4`;

                    // Скачиваем файл
                    const a = document.createElement('a');
                    a.href = url;
                    a.download = filename;
                    a.style.display = 'none';
                    document.body.appendChild(a);
                    a.click();

                    setTimeout(function() {
                        document.body.removeChild(a);
                        URL.revokeObjectURL(url);
                        progressBar.textContent = 'Готово!';
                        progressBar.style.width = '100%';
                    }, 100);
                };

                // Обработчик ошибок MP4Box
                mp4boxfile.onError = function(e) {
                    console.error('Ошибка MP4Box:', e);
                    progressBar.textContent = 'Ошибка создания MP4';
                    alert('Произошла ошибка при создании MP4 файла: ' + e);
                };

                // Добавляем все загруженные сегменты в MP4Box
                for (let i = 0; i < segmentData.length; i++) {
                    if (segmentData[i] instanceof Uint8Array) {
                        mp4boxfile.appendBuffer(segmentData[i].buffer);
                    }
                }

                // Завершаем файл
                mp4boxfile.flush();
            } catch (e) {
                console.error('Ошибка при создании MP4:', e);
                progressBar.textContent = 'Ошибка создания MP4';
                alert('Произошла ошибка при создании MP4 файла: ' + e.message);

                // Альтернативный метод - просто объединяем сегменты в один файл
                try {
                    const loadedSegments = segmentData.filter(segment => segment instanceof Uint8Array);
                    const totalLength = loadedSegments.reduce((total, segment) => total + segment.length, 0);
                    const combinedArray = new Uint8Array(totalLength);

                    let offset = 0;
                    for (let i = 0; i < segmentData.length; i++) {
                        if (segmentData[i] instanceof Uint8Array) {
                            combinedArray.set(segmentData[i], offset);
                            offset += segmentData[i].length;
                        }
                    }

                    const blob = new Blob([combinedArray], { type: 'video/mp4' });
                    const url = URL.createObjectURL(blob);

                    // Создаем имя файла на основе заголовка страницы и временного промежутка
                    const filename = `${pageTitle}_${startTime}_to_${endTime}_raw.mp4`;

                    // Скачиваем файл
                    const a = document.createElement('a');
                    a.href = url;
                    a.download = filename;
                    a.style.display = 'none';
                    document.body.appendChild(a);
                    a.click();

                    setTimeout(function() {
                        document.body.removeChild(a);
                        URL.revokeObjectURL(url);
                        progressBar.textContent = 'Готово (сырой файл)!';
                        progressBar.style.width = '100%';
                    }, 100);
                } catch (e2) {
                    console.error('Ошибка при создании сырого файла:', e2);
                    progressBar.textContent = 'Критическая ошибка';
                    alert('Не удалось создать файл: ' + e2.message);
                }
            }
        }