Проблемы с отлавливанием создания элементов, делаю расширение для ютуба

Я пытаюсь создать для себя расширение для ютуба. Его суть заключается в следующем:

  1. При первой загрузке видео мы устанавливаем скорость 1.5 для видео. (Я смотрю 99% видео именно в этой скорости).
  2. При первой загрузке видео, а так же при измении src (то есть.. если включили другой ролик) мы ставим ролик на паузу, пока не прогрузится достаточное кол-во контента (минимум 15 секунд).
  3. Снимаем ролик с паузы, если паузу поставил скрипт из-за недостаточного кол-ва загруженного контента, а так же мы находимся на данной странице (то есть.. чтобы видео не начало проигрывать, если мы открыли несколько вкладок).
  4. Добавляем показатель временной шкалы с учетом скорости.
  5. Добавляем input[type="range"] возле временной шкалы, для быстрого изменения скорости.
  6. Добавляем показатель значения скорости, а так же прошлого элемента управления (input).

В чем собственно проблема:

  1. Иногда при загрузке страницы, не добавляется временная шкала, а так же input. (Чаще это происходит, если вкладка не была активна.)
  2. Иногда при загрузке страницы добавляется временная шкала, а так же input, НО, они не реагируют не на что.
  3. Иногда не ставится пауза.
  4. Иногда не снимается пауза, когда достаточное кол-во контента загружено.
  5. Иногда снимается пауза, когда достаточное кол-во контента загружено, но сразу ставится опять на паузу и дальше не снимается пауза. (Возможно это происходит, когда пауза снимается, контента загружено ровно на 15 секунд и опять срабыватывает авто пауза и при этом же срабатывает догрузка контента, но это как-то остается незамеченным.)

Вероятнее всего я что-то упустил в описании.


Прикладываю код:
main.js (Оставил некоторые комментарии)

const skipNodeName = ['#text', '#comment'];
let playbackSpeed = 1.5;
let speedValue;
let slider;
let video;
let timeDurationWithSpeed;
let autoPause = 0;


const observer = new MutationObserver((mutations) =>
{
    mutations.forEach(mutation =>
    {
        if (mutation.type == 'childList')
        {
            for (let node of mutation.addedNodes)
            {
                if (!(node instanceof HTMLElement))
                {
                    continue;
                }

                if (node.classList.contains('html5-main-video'))
                {
                    // Если созданный элемент имеет класс html5-main-video, значит это ролик на ютубе
                    video = node;

                    if (!document.hidden)
                    {
                        autoPause = true;
                    }

                    // Ставим ролик на паузу
                    video.pause();

                    // Слушаем события
                    video.addEventListener('timeupdate', onChangeTime); // следим за изменением времени
                    video.addEventListener('ratechange', onChangeSpeed); // следим за изменением скорости
                    video.addEventListener('play', onChangeStatusPlaying); // следим за состоянием проигрывания
                    video.addEventListener('progress', onProgress); // следим за состоянием буфферизации

                    // Устанавливаем стандартную скорость для ролика
                    video.playbackRate = playbackSpeed;
                }
                else if (node.classList.contains('ytp-time-duration'))
                {
                    // Добавляем после временной шкалы ютуба следующие элементы:

                    // Слайдер, им мы изменяем скорость
                    slider           = document.createElement('input');
                    slider.type      = 'range';
                    slider.min       = 0.25;
                    slider.max       = 2.5;
                    slider.step      = 0.05;
                    slider.value     = playbackSpeed;
                    slider.className = 'sliderSpeed';
                    node.after(slider);

                    // Показатель текущей значения слайдера/скорости
                    speedValue           = document.createElement('span');
                    speedValue.innerText = `x${playbackSpeed}`;
                    speedValue.className = 'sliderSpeed__value';
                    node.after(speedValue);

                    // Показываем скорости прошло времени/сколько длится видео со скоростью
                    timeDurationWithSpeed           = document.createElement('span');
                    timeDurationWithSpeed.innerText = `(${secondToTime(video.currentTime / playbackSpeed)} / ${secondToTime(video.duration / playbackSpeed)})`;
                    timeDurationWithSpeed.className = 'timeDurationWithSpeed';
                    node.after(timeDurationWithSpeed);

                    // Слушаем прокрутку колесиком мышки над слайдером, дабы более точно выставлять значение
                    slider.addEventListener('wheel', (e) =>
                    {
                        const value = parseFloat(slider.value);
                        const step = parseFloat(slider.step);
                        const sign = Math.sign(e.deltaY);

                        slider.value = value - sign * step;

                        slider.dispatchEvent(new Event('input', { bubbles: true }));

                        e.preventDefault();
                    });

                    // Следим за изменением значения у слайдера и устанавливаем его value как скорость видео
                    slider.addEventListener('input', (e) =>
                    {
                        video.playbackRate = parseFloat(e.target.value);
                    });

                    // Добавляем стили
                    insertStyle();
                }
            }
        }
        else if (mutation.type == 'attributes')
        {
            if ((mutation.target instanceof HTMLElement) && mutation.target.classList.contains('html5-main-video'))
            {
                // Если изменился атрибут src у тега video, тогда ставим видео на паузу

                // let urlParams = new URLSearchParams(window.location.search.substr(1));
                // if (urlParams.has('list') || urlParams.has('index'))
                // {
                //  autoPause = true;
                // }

                if (!document.hidden)
                {
                    autoPause = true;
                }

                video.pause();
            }
        }
    });
});

// Запускаем обсервер
observer.observe(document, { subtree: true, childList: true, attributeFilter: ['src']});

// В функциях onProgress и onChangeStatusPlaying, мы проверяем, на сколько прогружено видео
// Если прогруженный кусочек меньше 15 секунд, то функция isNotEnoughBuffered вернет true
// Если же прогружено больше 15 секунд - мы снимаем с паузы, при условии, что до этого скрипт ставил на паузу

function onProgress()
{
    if (isNotEnoughBuffered())
    {
        if (!video.paused)
        {
            autoPause = true;
            video.pause();
        }
    }
    else if (autoPause)
    {
        autoPause = false;

        if (!document.hidden)
        {
            video.play();
        }
    }
}

function onChangeStatusPlaying()
{
    if (isNotEnoughBuffered())
    {
        if (!video.paused)
        {
            autoPause = true;
            video.pause();
        }
    }
}

// При изменении скорости видео мы меняем значение у value слайдера (если изменение не через него)
// А так же меняем значение временной шкалы со скоростью
function onChangeSpeed()
{
    playbackSpeed = this.playbackRate;
    if (!speedValue || !slider || !timeDurationWithSpeed)
    {
        return;
    }

    if (slider.value != playbackSpeed)
    {
        slider.value = playbackSpeed;
    }

    speedValue.innerText    = `x${playbackSpeed.toFixed(2)}`;
    timeDurationWithSpeed.innerText = `(${secondToTime(video.currentTime / playbackSpeed)} / ${secondToTime(video.duration / playbackSpeed)})`
}


// При изменении текущего времени видео меняем значение временной шкалы со скоростью
function onChangeTime()
{
    if (isNotEnoughBuffered())
    {
        video.pause();
    }

    if (!timeDurationWithSpeed)
    {
        return;
    }

    timeDurationWithSpeed.innerText = `(${secondToTime(video.currentTime / playbackSpeed)} / ${secondToTime(video.duration / playbackSpeed)})`
}

/**
 * Приводим секунды к формату чч:мм:сс
 * @param {number} second Кол-во секунд
 * @returns {string} чч:мм:сс
 */
function secondToTime(second)
{
    const hour = second / 3600 ^ 0;
    const min = (second - hour * 3600) / 60 ^ 0;
    const sec = second - hour * 3600 - min * 60 ^ 0;

    return (hour ? (hour < 10 ? '0' + hour : hour) +':' : '') +(min < 10 ? '0' +min : min) +':' +(sec < 10 ? '0' +sec : sec);
}

/**
 * Возвращает текущий buffered индекс
 * @returns {number}
 */
function getThisBufferedIndex()
{
    let buffered = video.buffered;
    let currentTime = video.currentTime;

    let index = buffered.length - 1;
    for (index; index >= 0; index--)
    {
        if (buffered.start(index) <= currentTime && buffered.end(index) >= currentTime)
        {
            return index;
        }
    }

    return 0;
}

/**
 * Достаточно ли прогружено видео
 * @returns {boolean}
 */
function isNotEnoughBuffered()
{
    let bufferedEnd;
    let bufferedIndex = getThisBufferedIndex();
    try
    {
        bufferedEnd = video.buffered.end(bufferedIndex);
    }
    catch(e)
    {
        return 1;
    }
    let timeToEnd = bufferedEnd - video.currentTime;

    return bufferedEnd < video.duration && timeToEnd < 15;
}

function insertStyle()
{
    let style = document.createElement('style');

    style.innerText =
        `
        .sliderSpeed
        {
            margin-left: 5px;
            -webkit-appearance: none;
            outline: none;
            background: transparent;
        }
        .sliderSpeed::-webkit-slider-runnable-track
        {
            width: 100%;
            height: 10px;
            cursor: pointer;
            background: #181818;
            border-radius: 5px;
            border: 1px solid #DC4242;
        }
        .sliderSpeed::-webkit-slider-thumb
        {
            -webkit-appearance: none;
            height: 15px;
            width: 15px;
            cursor: pointer;
            border-radius: 50px;
            background: #DC4242;
            border: 1px solid #181818;
            margin-top: -3px;
        }
        .sliderSpeed__value
        {
            margin-left: 10px;
            color: #DC4242;
        }
        .timeDurationWithSpeed
        {
            margin-left: 5px;
        }
        `;

    document.head.appendChild(style);
}

manifest.json

{
    "manifest_version": 2,

    "name": "YouTube Helper",
    "description": "Desc",
    "version": "1.0",

    "browser_action":
    {
        "default_icon": "icon.png"
    },

    "content_scripts":
    [
        {
            "matches":["*://*/*"],
            "run_at": "document_start",
            "js":
            [
                "main.js"
            ]
        }
    ],

    "permissions":
    [
        "activeTab",
        "tabs"
    ]
}

Ответы (0 шт):