YouTube - Minirreproductor

Muestra un minirreproductor al desplazarte más allá del video.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name                    YouTube - Miniplayer
// @name:fr                 YouTube - Lecteur réduit
// @name:es                 YouTube - Minirreproductor
// @name:de                 YouTube - Miniplayer
// @name:it                 YouTube - Miniplayer
// @name:zh-CN              YouTube - Miniplayer
// @namespace               https://gist.github.com/4lrick/5a54e121bdc9056a7551529669d65ae6
// @version                 1.1
// @description             Shows a mini player when you scroll past the video.
// @description:fr          Affiche un lecteur réduit quand vous faites défiler sous la vidéo.
// @description:es          Muestra un minirreproductor al desplazarte más allá del video.
// @description:de          Zeigt einen Miniplayer, sobald Sie am Video vorbeiscrollen.
// @description:it          Mostra un miniplayer quando scorri oltre il video.
// @description:zh-CN       在滚动越过视频时显示迷你播放器。
// @author                  4lrick
// @match                   https://www.youtube.com/*
// @icon                    https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant                   none
// @license                 GPL-3.0-only
// ==/UserScript==

(function () {
    'use strict';

    const CFG = {
        rawVideoSelector: '.html5-video-container',
        mainPlayerSelector: '#movie_player',
        miniPlayerClass: 'mpu-mini-player',
        widthPx: 480,
        scrollThreshold: 300,
        autohideDelayMs: 2000,
        transitionDurationMs: 300,
    };

    function injectStyle() {
        if (document.getElementById('mpu-style')) return;
        const style = document.createElement('style');
        style.id = 'mpu-style';
        style.textContent = `
        /* Mini player container */
        .${CFG.miniPlayerClass} {
            position: fixed !important;
            width: ${CFG.widthPx}px !important;
            height: ${Math.round(CFG.widthPx * 9 / 16)}px !important;
            right: 16px !important;
            bottom: 16px !important;
            background: #000;
            overflow: hidden !important;
            border-radius: 12px !important;
            opacity: 0;
            transform: scale(0.8) !important;
            transition: opacity ${CFG.transitionDurationMs}ms ease, transform ${CFG.transitionDurationMs}ms ease !important;
            user-select: none;
        }
        .${CFG.miniPlayerClass}.visible {
            opacity: 1;
            transform: scale(1) !important;
        }

        /* Ensure the video fills the mini player */
        .${CFG.miniPlayerClass} ${CFG.rawVideoSelector},
        .${CFG.miniPlayerClass} video.html5-main-video {
            width: 100% !important;
            height: 100% !important;
            left: 0 !important;
        }
        .${CFG.miniPlayerClass} ${CFG.rawVideoSelector} {
            position: static !important;
        }

        /* Controls bar */
        .${CFG.miniPlayerClass} .mpu-controls {
            position: absolute !important;
            left: 0; right: 0; bottom: 0;
            display: flex; align-items: center; gap: 8px;
            color: #fff; font: 12px/1.2 system-ui, sans-serif;
            user-select: none; pointer-events: auto;
            padding: 4px;
        }

        /* Bottom buttons */
        .${CFG.miniPlayerClass} .mpu-btn {
            cursor: pointer;
        }
        .${CFG.miniPlayerClass} .mpu-btn-play,
        .${CFG.miniPlayerClass} .mpu-btn-fullscreen {
            background: transparent; border: none;
            display: flex;
        }
        .${CFG.miniPlayerClass} .mpu-btn-play::before {
            content: '';
            width: 28px; height: 28px;
            background-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg"><path d="M12,26 L18.5,22 L18.5,14 L12,10 Z M18.5,22 L25,18 L25,18 L18.5,14 Z" fill="%23fff"/></svg>');
            background-size: contain;
            background-repeat: no-repeat;
        }
        .${CFG.miniPlayerClass} .mpu-btn-play.playing::before {
            background-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg"><path d="M12,26 L16,26 L16,10 L12,10 Z M21,26 L25,26 L25,10 L21,10 Z" fill="%23fff"/></svg>');
        }
        .${CFG.miniPlayerClass} .mpu-btn-fullscreen { margin-left: auto; }
        .${CFG.miniPlayerClass} .mpu-btn-fullscreen::before {
            content: '';
            width: 28px; height: 28px;
            background-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg"><path d="M10,16 h2 v-4 h4 v-2 h-6 v6 z" fill="%23fff"/><path d="M20,10 v2 h4 v4 h2 v-6 h-6 z" fill="%23fff"/><path d="M24,24 h-4 v2 h6 v-6 h-2 v4 z" fill="%23fff"/><path d="M12,20 h-2 v6 h6 v-2 h-4 v-4 z" fill="%23fff"/></svg>');
            background-size: contain;
            background-repeat: no-repeat;
        }
        .${CFG.miniPlayerClass} .mpu-btn-fullscreen.fullscreen::before {
            background-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg"><path d="M14,14 h-4 v2 h6 v-6 h-2 v4 z" fill="%23fff"/><path d="M22,14 v-4 h-2 v6 h6 v-2 h-4 z" fill="%23fff"/><path d="M20,26 h2 v-4 h4 v-2 h-6 v6 z" fill="%23fff"/><path d="M10,22 h4 v4 h2 v-6 h-6 v2 z" fill="%23fff"/></svg>');
        }

        /* Top buttons */
        .${CFG.miniPlayerClass} .mpu-btn-top {
            position: absolute; top: 0 !important;
            background: transparent; border: none;
            display: flex;
            padding: 8px;
        }
        .${CFG.miniPlayerClass} .mpu-btn-close { right: 0; }
        .${CFG.miniPlayerClass} .mpu-btn-scroll-up::before {
            content: '';
            width: 24px; height: 24px;
            background-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g transform="translate(12,12) scale(-1,1) translate(-12,-12)"><path d="M19,19 L5,19 L5,5 L12,5 L12,3 L5,3 C3.89,3 3,3.9 3,5 L3,19 C3,20.1 3.89,21 5,21 L19,21 C20.1,21 21,20.1 21,19 L21,12 L19,12 L19,19 Z M14,3 L14,5 L17.59,5 L7.76,14.83 L9.17,16.24 L19,6.41 L19,10 L21,10 L21,3 L14,3 Z" fill="%23fff"/></g></svg>');
            background-size: contain;
            background-repeat: no-repeat;
        }
        .${CFG.miniPlayerClass} .mpu-btn-close::before {
            content: '';
            width: 24px; height: 24px;
            background-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="%23fff"/></svg>');
            background-size: contain;
            background-repeat: no-repeat;
        }

        /* Autohide behavior */
        .${CFG.miniPlayerClass} .mpu-controls,
        .${CFG.miniPlayerClass} .mpu-btn-top,
        .${CFG.miniPlayerClass} .mpu-progress-container,
        .${CFG.miniPlayerClass} .ytp-gradient { transition: opacity 150ms ease; }

        .${CFG.miniPlayerClass}.ytp-autohide .mpu-controls,
        .${CFG.miniPlayerClass}.ytp-autohide .mpu-btn-top,
        .${CFG.miniPlayerClass}.ytp-autohide .mpu-progress-container,
        .${CFG.miniPlayerClass}.ytp-autohide .ytp-gradient {
            opacity: 0; pointer-events: none;
        }

        /* Progress bar */
        .${CFG.miniPlayerClass} .mpu-progress-container {
        position: absolute; left: 8px; right: 8px; bottom: 40px;
        }
        @-moz-document url-prefix() {
            .${CFG.miniPlayerClass} .mpu-progress-container {
            bottom: 30px;
            }
        }

        .${CFG.miniPlayerClass} .mpu-slider {
            --bar-h: 4px;
            --thumb: 13px;
            --fill: 0%;

            --accent: var(--yt-spec-static-brand-red, #f03);
            --accent2: #ff2791;
            --track-bg: rgba(255,255,255,0.2);
            --fill-gradient: linear-gradient(to right, var(--accent) 80%, var(--accent2) 100%);

            -webkit-appearance: none;
            width: -webkit-fill-available;
            width: -moz-available;
            cursor: pointer;
            background: none;
        }
        .${CFG.miniPlayerClass} .mpu-slider:hover { --bar-h: 6px; }

        .${CFG.miniPlayerClass} .mpu-slider::-webkit-slider-runnable-track {
            height: var(--bar-h);
            background:
            var(--fill-gradient) 0 50% / var(--fill) var(--bar-h) no-repeat,
            linear-gradient(var(--track-bg), var(--track-bg)) 0 50% / 100% var(--bar-h) no-repeat;
        }
        .${CFG.miniPlayerClass} .mpu-slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: var(--thumb); height: var(--thumb);
            border-radius: 50%;
            background: var(--accent);
            margin-top: calc((var(--bar-h) - var(--thumb)) / 2);
        }

        .${CFG.miniPlayerClass} .mpu-slider::-moz-range-track {
            height: var(--bar-h);
            background: var(--track-bg);
        }
        .${CFG.miniPlayerClass} .mpu-slider::-moz-range-progress {
            height: var(--bar-h);
            background: var(--fill-gradient);
        }
        .${CFG.miniPlayerClass} .mpu-slider::-moz-range-thumb {
            width: var(--thumb); height: var(--thumb);
            border: 0; border-radius: 50%;
            background: var(--accent);
        }

        /* Gradient behind controls */
        .${CFG.miniPlayerClass} .ytp-gradient {
            position: absolute; left: 0; right: 0;
            height: 64px; pointer-events: none;
            background: linear-gradient(to top, rgba(0,0,0,0.9), rgba(0,0,0,0));
        }

    `;
        document.head.appendChild(style);
    }

    let rawVideo;
    let miniPlayer;
    let playerInitialLocation = null;
    let controls = null;
    let isPlayerHidden = false;
    let autohideTimer = null;
    let observer = null;
    let loopStarted = false;
    let isDragging = false;
    let dragStartX = 0;
    let dragStartY = 0;
    let dragStartLeft = 0;
    let dragStartTop = 0;

    function restoreFromMiniPlayer() {
        if (!rawVideo) return;
        if (!playerInitialLocation) return;
        const parent = playerInitialLocation.parentNode;
        if (!parent) return;
        parent.insertBefore(rawVideo, playerInitialLocation);
        if (miniPlayer) {
            miniPlayer.classList.remove('visible');
            setTimeout(() => {
                miniPlayer.remove();
                miniPlayer = null;
                controls = null;
            }, CFG.transitionDurationMs);
        }
        playerInitialLocation = null;
    }

    function attachAutohideListeners() {
        miniPlayer.addEventListener('mousemove', showControlsNow);
        miniPlayer.addEventListener('mouseenter', showControlsNow);
        miniPlayer.addEventListener('touchstart', showControlsNow, { passive: true });
        miniPlayer.addEventListener('focusin', showControlsNow);
        miniPlayer.addEventListener('mouseleave', scheduleAutohide);
    }

    function attachFullscreenListener() {
        if (!controls) return;
        const { fullscreenBtn } = controls;
        function onFullscreenChange() {
            if (document.fullscreenElement) {
                fullscreenBtn.classList.add('fullscreen');
                fullscreenBtn.title = 'Exit full screen';
            } else {
                fullscreenBtn.classList.remove('fullscreen');
                fullscreenBtn.title = 'Full screen';
            }
        }
        document.addEventListener('fullscreenchange', onFullscreenChange);
    }

    function updatePlayIcon() {
        if (!controls) return;
        const video = getVideo();
        const isPlaying = video && !video.paused;
        if (isPlaying) {
            controls.playBtn.classList.add('playing');
            controls.playBtn.title = 'Pause';
        } else {
            controls.playBtn.classList.remove('playing');
            controls.playBtn.title = 'Play';
        }
    }

    function attachVideoListeners() {
        const video = getVideo();
        if (!video) return;
        video.addEventListener('play', updatePlayIcon);
        video.addEventListener('pause', updatePlayIcon);
        video.addEventListener('timeupdate', () => { updateBars(); updateTime(); });
        updatePlayIcon();
        updateBars();
        updateTime();
    }

    function formatTime(t) {
        if (!isFinite(t) || t < 0) return '--:--';
        const s = Math.floor(t % 60).toString().padStart(2, '0');
        const m = Math.floor((t / 60) % 60).toString();
        const h = Math.floor(t / 3600);
        return h > 0 ? `${h}:${m.padStart(2, '0')}:${s}` : `${m}:${s}`;
    }

    function updateTime() {
        if (!controls) return;
        const { time } = controls;
        const video = getVideo();
        if (video) {
            time.textContent = `${formatTime(video.currentTime)} / ${formatTime(video.duration)}`;
        } else {
            time.textContent = '--:-- / --:--';
        }
    }

    function isLive() {
        const mainPlayer = document.querySelector(CFG.mainPlayerSelector);
        if (!mainPlayer) return false;
        const timeDisplay = mainPlayer.querySelector('.ytp-time-display');
        return !!(timeDisplay && timeDisplay.classList.contains('ytp-live'));
    }

    function updateBars() {
        if (!controls) return;
        const { progressBar } = controls;
        const video = getVideo();
        if (!video) return;
        let progressFraction = Math.max(0, Math.min(1, video.currentTime / video.duration));
        progressBar.slider.value = progressFraction;
        progressBar.slider.style.setProperty('--fill', `${progressFraction * 100}%`);
    }

    function attachControlListeners() {
        if (!controls) return;
        const { playBtn, fullscreenBtn, scrollUpBtn, closeBtn, progressBar } = controls;

        function onPlayClick() {
            const video = getVideo();
            if (!video) return;
            if (video.paused) video.play(); else video.pause();
            showControlsNow();
        }

        function onFullscreenClick() {
            if (document.fullscreenElement) document.exitFullscreen();
            else displayMiniPlayer().requestFullscreen();
            showControlsNow();
        }

        function onUpClick() {
            window.scrollTo({ top: 0, behavior: 'smooth' });
            showControlsNow();
        }

        function onCloseClick() {
            isPlayerHidden = true;
            toggleMiniPlayer(false);
        }

        function onProgressInput(event) {
            const newProgressFraction = parseFloat(event.target.value);
            const video = getVideo();
            if (!video) return;
            video.currentTime = newProgressFraction * video.duration;
            updateBars();
            updateTime();
            showControlsNow();
        }

        playBtn.addEventListener('click', onPlayClick);
        fullscreenBtn.addEventListener('click', onFullscreenClick);
        scrollUpBtn.addEventListener('click', onUpClick);
        closeBtn.addEventListener('click', onCloseClick);
        progressBar.slider.addEventListener('input', onProgressInput);
    }

    function createGradient(position = 'bottom') {
        const gradient = document.createElement('div');
        gradient.className = 'ytp-gradient';
        if (position === 'top') {
            gradient.style.top = '-20px';
            gradient.style.transform = 'rotate(180deg)';
        } else {
            gradient.style.bottom = '-5px';
        }
        return gradient;
    }

    function createProgressBar() {
        const progressContainer = document.createElement('div');
        progressContainer.className = 'mpu-progress-container';

        const slider = document.createElement('input');
        slider.className = 'mpu-slider';
        slider.type = 'range';
        slider.min = '0';
        slider.max = '1';
        slider.step = '0.001';
        slider.value = '0';

        progressContainer.appendChild(slider);

        return {
            progressContainer,
            slider
        };
    }

    function createButton(type, text) {
        const btn = document.createElement('button');
        let className = `mpu-btn mpu-btn-${type}`;

        if (type === 'scroll-up' || type === 'close') {
            className += ' mpu-btn-top';
        }

        btn.className = className;
        btn.title = text;
        return btn;
    }

    function displayControls() {
        if (controls && controls.root && miniPlayer && miniPlayer.contains(controls.root)) return controls;

        const mainPlayer = document.querySelector(CFG.mainPlayerSelector);
        if (!mainPlayer) return null;

        const bottomGradient = createGradient('bottom');
        const topGradient = createGradient('top');
        miniPlayer.appendChild(bottomGradient);
        miniPlayer.appendChild(topGradient);

        const controlsRoot = document.createElement('div');
        controlsRoot.className = 'mpu-controls';

        const playBtn = createButton('play', 'Play/Pause');
        const fullscreenBtn = createButton('fullscreen', 'Full screen');
        const scrollUpBtn = createButton('scroll-up', 'Scroll to top');
        const closeBtn = createButton('close', 'Close');

        const time = document.createElement('span');
        time.textContent = '--:-- / --:--';

        controlsRoot.append(playBtn, time, fullscreenBtn);
        miniPlayer.appendChild(controlsRoot);

        const progressBar = createProgressBar();
        miniPlayer.appendChild(progressBar.progressContainer);

        miniPlayer.appendChild(scrollUpBtn);
        miniPlayer.appendChild(closeBtn);
        controls = { root: controlsRoot, playBtn, fullscreenBtn, time, scrollUpBtn, closeBtn, progressBar, bottomGradient, topGradient };
        return controls;
    }

    function onTimestampClickPreserveScroll(event) {
        if (isPlayerHidden) return;
        const anchor = event.target?.closest('a[href]');
        if (!anchor?.closest('#comments, ytd-comments')) return;

        const text = anchor.textContent?.trim();
        if (!text) return;

        const parts = text.split(':').map(p => parseInt(p, 10));
        if (parts.some(n => isNaN(n))) return;

        let seconds;
        if (parts.length === 2) {
            seconds = parts[0] * 60 + parts[1];
        } else if (parts.length === 3) {
            seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
        } else {
            return;
        }

        const videoElement = rawVideo?.querySelector('video');
        if (!videoElement?.duration) return;

        event.preventDefault();
        event.stopPropagation();

        videoElement.currentTime = Math.max(0, Math.min(videoElement.duration, seconds));
        showControlsNow();
    }

    function clearAutohideTimer() {
        if (autohideTimer) {
            clearTimeout(autohideTimer); autohideTimer = null;
        }
    }

    function scheduleAutohide() {
        clearAutohideTimer();
        autohideTimer = setTimeout(() => {
            if (miniPlayer) miniPlayer.classList.add('ytp-autohide');
        }, CFG.autohideDelayMs);
    }

    function showControlsNow() {
        miniPlayer.classList.remove('ytp-autohide');
        scheduleAutohide();
    }

    function getVideo() {
        return rawVideo ? rawVideo.querySelector('video') : null;
    }

    function onMiniPlayerBackgroundClick(event) {
        if (event.button !== 0) return;

        const mouseMovement = Math.abs(event.clientX - dragStartX) + Math.abs(event.clientY - dragStartY);
        if (mouseMovement > 1) return;

        const target = event.target;
        if (!target) return;
        if (isTargetInteractive(target)) return;
        const videoElement = getVideo();
        if (!videoElement) return;
        if (videoElement.paused) videoElement.play(); else videoElement.pause();
        showControlsNow();
    }

    function handleDragEnd() {
        isDragging = false;
        document.removeEventListener('mousemove', handleDragMove);
        document.removeEventListener('mouseup', handleDragEnd);
    }

    function handleDragMove(event) {
        if (!isDragging) return;

        const deltaX = event.clientX - dragStartX;
        const deltaY = event.clientY - dragStartY;

        const newLeft = Math.max(0, Math.min(window.innerWidth - CFG.widthPx, dragStartLeft + deltaX));
        const newTop = Math.max(0, Math.min(window.innerHeight - Math.round(CFG.widthPx * 9 / 16), dragStartTop + deltaY));

        miniPlayer.style.left = newLeft + 'px';
        miniPlayer.style.top = newTop + 'px';
    }

    function isTargetInteractive(target) {
        if (!target || !target.closest) return false;
        return !!(
            target.closest('.mpu-progress-container') ||
            target.closest('button')
        );
    }

    function handleDragStart(event) {
        if (event.button !== 0 || isTargetInteractive(event.target) || document.fullscreenElement === miniPlayer) return;

        isDragging = true;
        dragStartX = event.clientX;
        dragStartY = event.clientY;

        const rect = miniPlayer.getBoundingClientRect();
        dragStartLeft = rect.left;
        dragStartTop = rect.top;

        document.addEventListener('mousemove', handleDragMove);
        document.addEventListener('mouseup', handleDragEnd);

        event.preventDefault();
    }

    function displayMiniPlayer() {
        if (miniPlayer && document.body.contains(miniPlayer)) return miniPlayer;
        miniPlayer = document.createElement('div');
        miniPlayer.className = CFG.miniPlayerClass;
        document.body.appendChild(miniPlayer);
        miniPlayer.addEventListener('mousedown', handleDragStart);
        miniPlayer.addEventListener('click', onMiniPlayerBackgroundClick);
        document.addEventListener('click', onTimestampClickPreserveScroll, true);

        requestAnimationFrame(() => {
            miniPlayer.classList.add('visible');
        });

        return miniPlayer;
    }

    function moveToMiniPlayer() {
        if (!rawVideo || !window.location.pathname.includes('watch')) return;

        const mainPlayer = document.querySelector(CFG.mainPlayerSelector);
        const activeVideo = mainPlayer ? mainPlayer.querySelector(CFG.rawVideoSelector) : null;
        const offlineSlate = mainPlayer ? mainPlayer.querySelector('.ytp-offline-slate') : null;
        if (!activeVideo || (offlineSlate && offlineSlate.style.display !== 'none')) return;
        if (isLive()) return;

        rawVideo = activeVideo;

        playerInitialLocation = rawVideo.nextSibling;
        displayMiniPlayer().appendChild(rawVideo);
        displayControls();
        attachControlListeners();
        attachVideoListeners();
        attachFullscreenListener();
        attachAutohideListeners();
        showControlsNow();
    }

    function toggleMiniPlayer(enabled) {
        if (!rawVideo) return;

        if (enabled) {
            moveToMiniPlayer();
        } else {
            restoreFromMiniPlayer();
        }
    }

    function handleVisibility(inView) {
        if (!inView) isPlayerHidden = false;
        if (isPlayerHidden && inView) return;
        toggleMiniPlayer(inView);
    }

    function tick() {
        if (!rawVideo) return;

        handleVisibility(window.scrollY > CFG.scrollThreshold);
        requestAnimationFrame(tick);
    }

    function checkVideoExists() {
        const mainPlayer = document.querySelector(CFG.mainPlayerSelector);
        const inWatchPage = window.location.pathname.includes('watch');
        const foundVideo = mainPlayer ? mainPlayer.querySelector(CFG.rawVideoSelector) : null;
        const newVideo = (inWatchPage && foundVideo) ? foundVideo : null;
        if (rawVideo !== newVideo) {
            rawVideo = newVideo;
        }
        if (!rawVideo) return;
        if (!loopStarted) {
            loopStarted = true;
            if (observer) observer.disconnect();
            tick();
        }
    }

    function init() {
        injectStyle();
        if (observer) observer.disconnect();
        observer = new MutationObserver(checkVideoExists);
        observer.observe(document.body, { childList: true, subtree: true });
    }

    init();
})();