轻量级画中画扩展

轻量级全局顶置画中画功能,支持大多数视频网站和直播平台

// ==UserScript==
// @name         轻量级画中画扩展
// @version      2.0.2
// @description  轻量级全局顶置画中画功能,支持大多数视频网站和直播平台
// @author       嘉友友
// @match        *://*/*
// @license      GPL-3.0
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @icon         data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE5IDdINVYxN0gxM1YxNUg3VjlIMTdWMTFIMTlWN1oiIGZpbGw9IiMwMDAwMDAiLz4KPHBhdGggZD0iTTE1IDEzVjE3SDE5VjEzSDE1WiIgZmlsbD0iIzAwMDAwMCIvPgo8L3N2Zz4K
// @namespace https://greasyfork.org/users/1336389
// ==/UserScript==

(function() {
    'use strict';

    // 检查浏览器支持
    if (!document.pictureInPictureEnabled) {
        console.warn('画中画功能不被当前浏览器支持');
        return;
    }

    // 全局状态管理
    const State = {
        isProcessed: false,
        observer: null,
        currentPipVideo: null,
        floatingButton: null,
        isDragging: false,
        hideTimeout: null,
        animationFrame: null,
        isVisible: false,
        documentVisible: true,
        styleElement: null,
        currentNotification: null,
        cleanupTasks: new Set()
    };

    // 常量配置
    const CONFIG = {
        STORAGE_KEY: 'pip_button_global_position',
        DEFAULT_POSITION: { right: -30, top: 20, side: 'right' },
        VIDEO_CACHE_SIZE: 50,
        POSITION_CACHE_SIZE: 30,
        DEBOUNCE_DELAY: 300,
        THROTTLE_DELAY: 200,
        CHECK_INTERVAL: 2000,
        CACHE_TIMEOUT: 1000,
        DRAG_THRESHOLD: 5,
        MIN_VIDEO_SIZE: 100,
        HIDE_DELAY: 2000,
        NOTIFICATION_DURATION: 3000
    };

    // 工具函数库
    const Utils = {
        debounce(func, wait) {
            let timeout;
            const debounced = function executedFunction(...args) {
                if (timeout) clearTimeout(timeout);
                timeout = setTimeout(() => func.apply(this, args), wait);
            };
            debounced.cancel = () => timeout && clearTimeout(timeout);
            return debounced;
        },

        throttle(func, wait) {
            let lastTime = 0;
            return function executedFunction(...args) {
                const now = performance.now();
                if (now - lastTime >= wait) {
                    lastTime = now;
                    return func.apply(this, args);
                }
            };
        },

        rafThrottle(func) {
            let ticking = false;
            return function(...args) {
                if (!ticking) {
                    ticking = true;
                    requestAnimationFrame(() => {
                        func.apply(this, args);
                        ticking = false;
                    });
                }
            };
        },

        createLRUCache(maxSize) {
            const cache = new Map();
            return {
                get(key) {
                    if (cache.has(key)) {
                        const value = cache.get(key);
                        cache.delete(key);
                        cache.set(key, value);
                        return value;
                    }
                    return null;
                },
                set(key, value) {
                    if (cache.has(key)) {
                        cache.delete(key);
                    } else if (cache.size >= maxSize) {
                        const firstKey = cache.keys().next().value;
                        cache.delete(firstKey);
                    }
                    cache.set(key, value);
                },
                has(key) {
                    return cache.has(key);
                },
                clear() {
                    cache.clear();
                }
            };
        },

        scheduleIdleTask(task, timeout = CONFIG.CHECK_INTERVAL) {
            if (typeof requestIdleCallback === 'function') {
                return requestIdleCallback(task, { timeout });
            } else {
                return setTimeout(task, Math.min(timeout, 100));
            }
        }
    };

    // 缓存管理器
    const CacheManager = {
        _videoCache: new WeakMap(),
        _positionCache: Utils.createLRUCache(CONFIG.POSITION_CACHE_SIZE),
        _lastCleanup: 0,
        _cleanupInterval: 30000,

        cacheVideoResult(video, isValid) {
            this._videoCache.set(video, {
                isValid,
                timestamp: performance.now()
            });
        },

        getCachedVideoResult(video) {
            const cached = this._videoCache.get(video);
            if (cached && (performance.now() - cached.timestamp) < CONFIG.CACHE_TIMEOUT) {
                return cached.isValid;
            }
            return null;
        },

        cachePosition(key, position) {
            this._positionCache.set(key, position);
        },

        getCachedPosition(key) {
            return this._positionCache.get(key);
        },

        cleanup() {
            const now = performance.now();
            if (now - this._lastCleanup > this._cleanupInterval) {
                this._positionCache.clear();
                this._lastCleanup = now;
            }
        }
    };

    // 视频检测器
    const VideoDetector = {
        _lastQueryTime: 0,
        _cachedVideos: null,
        _visibleVideos: new Set(),

        isValidVideo(video) {
            if (!video || video.tagName !== 'VIDEO' || !video.isConnected) {
                return false;
            }

            // 检查缓存
            const cached = CacheManager.getCachedVideoResult(video);
            if (cached !== null) {
                return cached;
            }

            // 执行检查
            const isValid = this._checkVideoValidity(video);
            CacheManager.cacheVideoResult(video, isValid);

            return isValid;
        },

        _checkVideoValidity(video) {
            // 检查视频源
            if (!video.src && !video.srcObject && !video.currentSrc && !video.querySelector('source')) {
                return false;
            }

            // 检查样式
            const style = getComputedStyle(video);
            if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
                return false;
            }

            // 检查尺寸
            const rect = video.getBoundingClientRect();
            return rect.width >= CONFIG.MIN_VIDEO_SIZE && rect.height >= CONFIG.MIN_VIDEO_SIZE;
        },

        getAllVideos() {
            const now = performance.now();

            // 使用缓存结果
            if (this._cachedVideos && (now - this._lastQueryTime) < CONFIG.CACHE_TIMEOUT) {
                return this._cachedVideos.filter(v => v.isConnected);
            }

            // 查找所有视频元素
            const allVideos = Array.from(document.querySelectorAll('video'));
            const videos = allVideos.filter(video => this.isValidVideo(video));

            this._cachedVideos = videos;
            this._lastQueryTime = now;

            console.log('找到视频数量:', videos.length);
            return videos;
        },

        getCurrentVideo() {
            if (State.currentPipVideo && this.isValidVideo(State.currentPipVideo)) {
                return State.currentPipVideo;
            }

            const videos = this.getAllVideos();
            if (videos.length === 0) return null;

            // 优先返回正在播放的视频
            for (const video of videos) {
                if (!video.paused && !video.ended) {
                    return video;
                }
            }

            // 其次返回有时长的视频
            for (const video of videos) {
                if (video.duration && video.duration !== Infinity) {
                    return video;
                }
            }

            return videos[0] || null;
        },

        cleanup() {
            this._visibleVideos.clear();
        }
    };

    // 全局位置管理器
    const PositionManager = {
        _cache: null,
        _lastViewport: { width: 0, height: 0 },

        save(position) {
            this._cache = position;
            try {
                const data = JSON.stringify(position);
                if (typeof GM_setValue !== 'undefined') {
                    GM_setValue(CONFIG.STORAGE_KEY, data);
                } else {
                    localStorage.setItem(CONFIG.STORAGE_KEY, data);
                }
            } catch (e) {
                console.warn('位置保存失败:', e);
            }
        },

        load() {
            if (this._cache) return this._cache;

            try {
                let stored = null;

                if (typeof GM_getValue !== 'undefined') {
                    stored = GM_getValue(CONFIG.STORAGE_KEY, null);
                }

                if (!stored && typeof localStorage !== 'undefined') {
                    stored = localStorage.getItem(CONFIG.STORAGE_KEY);
                    if (stored && typeof GM_setValue !== 'undefined') {
                        this.save(JSON.parse(stored));
                        localStorage.removeItem(CONFIG.STORAGE_KEY);
                    }
                }

                if (stored) {
                    const position = typeof stored === 'string' ? JSON.parse(stored) : stored;
                    this._cache = this.validatePosition(position);
                    return this._cache;
                }
            } catch (e) {
                console.warn('位置加载失败:', e);
            }

            this._cache = CONFIG.DEFAULT_POSITION;
            return this._cache;
        },

        validatePosition(position) {
            const viewport = { width: window.innerWidth, height: window.innerHeight };
            const cacheKey = `${position.top}-${position.side}-${position.left || position.right}-${viewport.width}-${viewport.height}`;

            // 检查缓存
            if (this._viewportUnchanged(viewport)) {
                const cached = CacheManager.getCachedPosition(cacheKey);
                if (cached) return cached;
            }

            // 计算有效位置
            const maxX = viewport.width - 50;
            const maxY = viewport.height - 50;

            const validatedPosition = {
                top: Math.max(10, Math.min(position.top || 20, maxY)),
                side: position.side || 'right'
            };

            if (position.side === 'right') {
                validatedPosition.right = Math.max(-30, Math.min(position.right || -30, maxX));
            } else {
                validatedPosition.left = Math.max(-30, Math.min(position.left || -30, maxX));
            }

            // 缓存结果
            CacheManager.cachePosition(cacheKey, validatedPosition);
            this._lastViewport = viewport;

            return validatedPosition;
        },

        _viewportUnchanged(viewport) {
            return viewport.width === this._lastViewport.width &&
                   viewport.height === this._lastViewport.height;
        },

        reset() {
            this._cache = null;
            try {
                if (typeof GM_deleteValue !== 'undefined') {
                    GM_deleteValue(CONFIG.STORAGE_KEY);
                }
                if (typeof localStorage !== 'undefined') {
                    localStorage.removeItem(CONFIG.STORAGE_KEY);
                }
            } catch (e) {
                console.warn('位置重置失败:', e);
            }
        }
    };

    // 样式管理器
    const StyleManager = {
        create() {
            if (State.styleElement) return;

            State.styleElement = document.createElement('style');
            State.styleElement.textContent = `
                #floating-pip-button {
                    position: fixed;
                    z-index: 99999;
                    background: rgba(0,0,0,0.8);
                    color: white;
                    border: none;
                    border-radius: 50%;
                    width: 50px;
                    height: 50px;
                    font-size: 20px;
                    cursor: move;
                    display: block;
                    font-family: monospace;
                    box-shadow: 0 2px 10px rgba(0,0,0,0.3);
                    user-select: none;
                    -webkit-user-select: none;
                    backdrop-filter: blur(5px);
                    -webkit-backdrop-filter: blur(5px);
                    transition: opacity 0.3s ease, transform 0.3s ease, right 0.3s ease, left 0.3s ease;
                    will-change: transform, opacity, right, left;
                    opacity: 0.6;
                    transform: scale(0.8);
                    touch-action: none;
                    contain: layout style paint;
                }

                #floating-pip-button:hover {
                    opacity: 1;
                    transform: scale(1);
                }

                #pip-notification {
                    position: fixed;
                    top: 80px;
                    left: 50%;
                    transform: translateX(-50%);
                    background: rgba(0,0,0,0.9);
                    color: white;
                    padding: 12px 20px;
                    border-radius: 6px;
                    z-index: 100000;
                    font-size: 14px;
                    pointer-events: none;
                    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
                    backdrop-filter: blur(5px);
                    -webkit-backdrop-filter: blur(5px);
                    animation: slideDown 0.3s ease;
                    contain: layout style paint;
                }

                @keyframes slideDown {
                    from {
                        opacity: 0;
                        transform: translateX(-50%) translateY(-20px);
                    }
                    to {
                        opacity: 1;
                        transform: translateX(-50%) translateY(0);
                    }
                }
            `;
            document.head.appendChild(State.styleElement);
        },

        cleanup() {
            if (State.styleElement?.parentNode) {
                State.styleElement.remove();
                State.styleElement = null;
            }
        }
    };

    // 事件管理器
    const EventManager = {
        _handlers: new Map(),

        init() {
            // 统一的事件委托
            this.addHandler(document, 'mouseenter', this._handleDelegatedEvent, { passive: true, capture: true });
            this.addHandler(document, 'mouseleave', this._handleDelegatedEvent, { passive: true, capture: true });
            this.addHandler(document, 'contextmenu', this._handleDelegatedEvent, { passive: false, capture: true });
            this.addHandler(document, 'keydown', this._handleKeyPress, { passive: false });
            this.addHandler(document, 'enterpictureinpicture', this._handlePipChange, { passive: true });
            this.addHandler(document, 'leavepictureinpicture', this._handlePipChange, { passive: true });
            this.addHandler(document, 'visibilitychange', this._handleVisibilityChange, { passive: true });
            this.addHandler(window, 'resize', Utils.throttle(this._handleResize, CONFIG.THROTTLE_DELAY), { passive: true });
            this.addHandler(window, 'beforeunload', this._handleBeforeUnload, { passive: true, once: true });
        },

        addHandler(target, event, handler, options = {}) {
            target.addEventListener(event, handler, options);
            if (!this._handlers.has(target)) {
                this._handlers.set(target, []);
            }
            this._handlers.get(target).push({ event, handler, options });
        },

        _handleDelegatedEvent(e) {
            if (e.target.id === 'floating-pip-button') {
                switch(e.type) {
                    case 'mouseenter':
                        ButtonController.show();
                        break;
                    case 'mouseleave':
                        ButtonController.scheduleHide();
                        break;
                    case 'contextmenu':
                        e.preventDefault();
                        ButtonController.resetPosition();
                        break;
                }
            }
        },

        _handleKeyPress(e) {
            if (e.key.toLowerCase() === 'p' && (e.ctrlKey || e.altKey)) {
                e.preventDefault();
                const video = VideoDetector.getCurrentVideo();
                if (video) {
                    PipController.toggle(video);
                } else {
                    NotificationManager.show('未找到可用的视频元素');
                }
            }

            if (e.key.toLowerCase() === 'r' && e.ctrlKey && e.altKey && e.shiftKey) {
                e.preventDefault();
                ButtonController.resetPosition();
            }
        },

        _handlePipChange() {
            ButtonController.updatePipState();
        },

        _handleVisibilityChange() {
            State.documentVisible = !document.hidden;
            if (State.documentVisible) {
                VideoManager.startDetection();
            } else {
                VideoManager.stopDetection();
            }
        },

        _handleResize() {
            if (State.floatingButton) {
                ButtonController.adjustPosition();
            }
        },

        _handleBeforeUnload() {
            cleanup();
        },

        cleanup() {
            for (const [target, handlers] of this._handlers) {
                for (const { event, handler } of handlers) {
                    target.removeEventListener(event, handler);
                }
            }
            this._handlers.clear();
        }
    };

    // 按钮控制器
    const ButtonController = {
        create() {
            const button = document.createElement('button');
            button.innerHTML = '⧉';
            button.title = '画中画模式 (快捷键: Ctrl/Alt + P)\n右键重置位置';
            button.id = 'floating-pip-button';

            this.applyStoredPosition(button);
            this.setupDrag(button);

            return button;
        },

        applyStoredPosition(button) {
            const position = PositionManager.load();

            const styles = {
                top: position.top + 'px'
            };

            if (position.side === 'right') {
                styles.right = position.right + 'px';
                styles.left = 'auto';
            } else {
                styles.left = position.left + 'px';
                styles.right = 'auto';
            }

            Object.assign(button.style, styles);

            const isAttached = (position.side === 'right' && position.right <= 0) ||
                              (position.side === 'left' && position.left <= 0);

            if (!isAttached) {
                button.style.opacity = '1';
                button.style.transform = 'scale(1)';
                State.isVisible = true;
                this.scheduleHide();
            }
        },

        setupDrag(button) {
            let startX, startY, initialX, initialY;

            button.addEventListener('mousedown', handleMouseDown, { passive: false });

            function handleMouseDown(e) {
                if (e.button !== 0) return;

                startX = e.clientX;
                startY = e.clientY;

                const rect = button.getBoundingClientRect();
                initialX = rect.left;
                initialY = rect.top;

                const handleMouseMove = Utils.rafThrottle(handleDrag);

                function handleDrag(e) {
                    const deltaX = e.clientX - startX;
                    const deltaY = e.clientY - startY;

                    if (!State.isDragging && (Math.abs(deltaX) > CONFIG.DRAG_THRESHOLD || Math.abs(deltaY) > CONFIG.DRAG_THRESHOLD)) {
                        State.isDragging = true;
                        button.style.cursor = 'grabbing';
                        button.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
                        ButtonController.show();
                    }

                    if (State.isDragging) {
                        const newX = Math.max(0, Math.min(initialX + deltaX, window.innerWidth - 50));
                        const newY = Math.max(0, Math.min(initialY + deltaY, window.innerHeight - 50));

                        Object.assign(button.style, {
                            left: newX + 'px',
                            top: newY + 'px',
                            right: 'auto'
                        });
                    }
                }

                function handleMouseUp() {
                    document.removeEventListener('mousemove', handleMouseMove);
                    document.removeEventListener('mouseup', handleMouseUp);

                    if (State.isDragging) {
                        ButtonController.snapToEdgeAndSave();
                        State.isDragging = false;
                        button.style.cursor = 'move';
                        button.style.transition = 'opacity 0.3s ease, transform 0.3s ease, right 0.3s ease, left 0.3s ease';
                        ButtonController.scheduleHide();
                    } else {
                        PipController.toggle(VideoDetector.getCurrentVideo());
                    }
                }

                document.addEventListener('mousemove', handleMouseMove, { passive: false });
                document.addEventListener('mouseup', handleMouseUp, { passive: true });

                e.preventDefault();
            }
        },

        show() {
            if (!State.floatingButton || State.isVisible) return;

            clearTimeout(State.hideTimeout);

            if (State.animationFrame) {
                cancelAnimationFrame(State.animationFrame);
            }

            State.animationFrame = requestAnimationFrame(() => {
                const rect = State.floatingButton.getBoundingClientRect();
                const isAtRightEdge = rect.right >= window.innerWidth - 5;
                const isAtLeftEdge = rect.left <= 5;

                const styles = {
                    opacity: '1',
                    transform: 'scale(1)'
                };

                if (isAtRightEdge || State.floatingButton.style.right.includes('-')) {
                    styles.right = '10px';
                    styles.left = 'auto';
                } else if (isAtLeftEdge || State.floatingButton.style.left.includes('-')) {
                    styles.left = '10px';
                    styles.right = 'auto';
                }

                Object.assign(State.floatingButton.style, styles);
                State.isVisible = true;
            });
        },

        hide() {
            if (!State.floatingButton || State.isDragging || !State.isVisible) return;

            if (State.animationFrame) {
                cancelAnimationFrame(State.animationFrame);
            }

            State.animationFrame = requestAnimationFrame(() => {
                const rect = State.floatingButton.getBoundingClientRect();
                const centerX = rect.left + rect.width / 2;
                const isCloserToRight = centerX > window.innerWidth / 2;

                const styles = {
                    opacity: '0.6',
                    transform: 'scale(0.8)'
                };

                if (isCloserToRight) {
                    styles.right = '-30px';
                    styles.left = 'auto';
                } else {
                    styles.left = '-30px';
                    styles.right = 'auto';
                }

                Object.assign(State.floatingButton.style, styles);
                State.isVisible = false;
            });
        },

        scheduleHide: Utils.debounce(() => ButtonController.hide(), CONFIG.HIDE_DELAY),

        snapToEdgeAndSave() {
            if (!State.floatingButton) return;

            const rect = State.floatingButton.getBoundingClientRect();
            const centerX = rect.left + rect.width / 2;
            const isCloserToRight = centerX > window.innerWidth / 2;

            const maxTop = window.innerHeight - 60;
            const newTop = Math.max(10, Math.min(rect.top, maxTop));

            let position;
            const styles = {
                top: newTop + 'px',
                opacity: '0.6',
                transform: 'scale(0.8)'
            };

            if (isCloserToRight) {
                styles.right = '-30px';
                styles.left = 'auto';
                position = { right: -30, top: newTop, side: 'right' };
            } else {
                styles.left = '-30px';
                styles.right = 'auto';
                position = { left: -30, top: newTop, side: 'left' };
            }

            Object.assign(State.floatingButton.style, styles);
            State.isVisible = false;

            PositionManager.save(position);
        },

        adjustPosition() {
            PositionManager._cache = null;
            const position = PositionManager.load();
            const validatedPosition = PositionManager.validatePosition(position);

            const styles = { top: validatedPosition.top + 'px' };

            if (validatedPosition.side === 'right') {
                styles.right = validatedPosition.right + 'px';
                styles.left = 'auto';
            } else {
                styles.left = validatedPosition.left + 'px';
                styles.right = 'auto';
            }

            Object.assign(State.floatingButton.style, styles);
            PositionManager.save(validatedPosition);
        },

        resetPosition() {
            if (!State.floatingButton) return;

            PositionManager.reset();
            this.applyStoredPosition(State.floatingButton);
            NotificationManager.show('按钮位置已重置到吸附状态');

            this.show();
            setTimeout(() => this.scheduleHide(), CONFIG.HIDE_DELAY);
        },

        updatePipState() {
            const pipElement = document.pictureInPictureElement;

            if (!pipElement) {
                State.currentPipVideo = null;
            }

            if (State.floatingButton) {
                State.floatingButton.style.background = pipElement ?
                    'rgba(33,150,243,0.8)' : 'rgba(0,0,0,0.8)';

                State.floatingButton.title = pipElement ?
                    '退出画中画模式 (快捷键: Ctrl/Alt + P)\n右键重置位置' :
                    '画中画模式 (快捷键: Ctrl/Alt + P)\n右键重置位置';
            }
        }
    };

    // 画中画控制器
    const PipController = {
        async toggle(video) {
            try {
                if (document.pictureInPictureElement) {
                    await document.exitPictureInPicture();
                    NotificationManager.show('已退出画中画模式');
                } else if (video && !video.disablePictureInPicture) {
                    if (video.readyState === 0) {
                        NotificationManager.show('视频正在加载中,请稍后再试');
                        return;
                    }

                    await video.requestPictureInPicture();
                    State.currentPipVideo = video;
                    NotificationManager.show('已开启画中画模式');
                } else {
                    NotificationManager.show('未找到可用的视频');
                }
            } catch (error) {
                const errorMessages = {
                    'InvalidStateError': '视频暂时无法使用画中画功能',
                    'NotSupportedError': '该视频不支持画中画功能',
                    'NotAllowedError': '画中画功能被禁用,请检查浏览器设置'
                };

                NotificationManager.show(errorMessages[error.name] || '画中画功能暂不可用');
            }
        }
    };

    // 通知管理器
    const NotificationManager = {
        show(message) {
            if (State.currentNotification) {
                State.currentNotification.textContent = message;
                State.currentNotification.style.animation = 'none';
                State.currentNotification.offsetHeight;
                State.currentNotification.style.animation = 'slideDown 0.3s ease';
            } else {
                State.currentNotification = document.createElement('div');
                State.currentNotification.id = 'pip-notification';
                State.currentNotification.textContent = message;
                document.body.appendChild(State.currentNotification);
            }

            setTimeout(() => {
                if (State.currentNotification?.parentNode) {
                    State.currentNotification.style.opacity = '0';
                    State.currentNotification.style.transition = 'opacity 0.3s ease';
                    setTimeout(() => {
                        if (State.currentNotification?.parentNode) {
                            State.currentNotification.remove();
                            State.currentNotification = null;
                        }
                    }, 300);
                }
            }, CONFIG.NOTIFICATION_DURATION);
        }
    };

    // 视频管理器
    const VideoManager = {
        _checkTask: null,

        startDetection() {
            this.detectVideos();
            this._scheduleNextCheck();
        },

        stopDetection() {
            if (this._checkTask) {
                if (typeof this._checkTask === 'number') {
                    clearTimeout(this._checkTask);
                } else {
                    if (typeof cancelIdleCallback === 'function') {
                        cancelIdleCallback(this._checkTask);
                    }
                }
                this._checkTask = null;
            }
        },

        _scheduleNextCheck() {
            if (!State.documentVisible) return;

            this._checkTask = Utils.scheduleIdleTask(() => {
                if (State.documentVisible) {
                    this.detectVideos();
                    this._scheduleNextCheck();
                }
            }, CONFIG.CHECK_INTERVAL);
        },

        detectVideos: Utils.debounce(() => {
            if (!State.documentVisible) return;

            const videos = VideoDetector.getAllVideos();
            const hasVideo = videos.length > 0;

            console.log('检测到视频:', hasVideo, '按钮存在:', !!State.floatingButton);

            if (hasVideo !== !!State.floatingButton) {
                if (hasVideo && !State.floatingButton) {
                    console.log('创建按钮');
                    StyleManager.create();
                    State.floatingButton = ButtonController.create();
                    document.body.appendChild(State.floatingButton);
                } else if (!hasVideo && State.floatingButton) {
                    console.log('移除按钮');
                    State.floatingButton.remove();
                    State.floatingButton = null;
                    State.isVisible = false;
                }
            }

            // 清理缓存
            CacheManager.cleanup();
        }, CONFIG.DEBOUNCE_DELAY)
    };

    // DOM观察器
    const DOMObserver = {
        init() {
            const callback = Utils.debounce((mutations) => {
                if (!State.documentVisible) return;

                let shouldCheck = false;

                for (const mutation of mutations) {
                    if (mutation.type === 'childList') {
                        for (const node of mutation.addedNodes) {
                            if (node.nodeType === 1) {
                                if (node.tagName === 'VIDEO') {
                                    shouldCheck = true;
                                    break;
                                } else if (node.tagName === 'IFRAME' || (node.querySelector && node.querySelector('video'))) {
                                    shouldCheck = true;
                                    break;
                                }
                            }
                        }
                    }
                    if (shouldCheck) break;
                }

                if (shouldCheck) {
                    console.log('DOM变化触发视频检测');
                    VideoManager.detectVideos();
                }
            }, 500);

            State.observer = new MutationObserver(callback);
            State.observer.observe(document.documentElement, {
                childList: true,
                subtree: true,
                attributes: false,
                attributeOldValue: false
            });
        },

        cleanup() {
            if (State.observer) {
                State.observer.disconnect();
                State.observer = null;
            }
        }
    };

    // 清理函数
    function cleanup() {
        clearTimeout(State.hideTimeout);
        if (State.animationFrame) {
            cancelAnimationFrame(State.animationFrame);
        }

        VideoDetector.cleanup();
        VideoManager.stopDetection();
        DOMObserver.cleanup();
        EventManager.cleanup();
        StyleManager.cleanup();

        if (State.floatingButton?.parentNode) {
            State.floatingButton.remove();
        }
        if (State.currentNotification?.parentNode) {
            State.currentNotification.remove();
        }

        for (const task of State.cleanupTasks) {
            try { task(); } catch (e) { console.warn('清理任务失败:', e); }
        }
        State.cleanupTasks.clear();

        Object.assign(State, {
            isProcessed: false,
            observer: null,
            currentPipVideo: null,
            floatingButton: null,
            isDragging: false,
            hideTimeout: null,
            animationFrame: null,
            isVisible: false,
            documentVisible: true,
            styleElement: null,
            currentNotification: null
        });
    }

    // 初始化函数
    function initialize() {
        if (State.isProcessed) return;
        State.isProcessed = true;

        try {
            console.log('初始化画中画扩展');

            EventManager.init();
            DOMObserver.init();

            // 延迟开始检测,确保页面加载完成
            setTimeout(() => {
                VideoManager.startDetection();
            }, 1000);

            console.log('画中画扩展初始化完成');
        } catch (error) {
            console.error('画中画扩展初始化失败:', error);
            cleanup();
        }
    }

    // 启动脚本
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize, { once: true });
    } else {
        // 确保 DOM 完全准备好
        setTimeout(initialize, 100);
    }

})();