// ==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);
}
})();