// ==UserScript== // @name 网页视频倍速播放器·视频全页面播放适配 // @namespace https://toolsdar.cn/ // @version 0.2 // @description 将视频播放窗口适配为全页面显示 // @author Your name // @match *://*/* // @grant none // @run-at document-start // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/527052/%E7%BD%91%E9%A1%B5%E8%A7%86%E9%A2%91%E5%80%8D%E9%80%9F%E6%92%AD%E6%94%BE%E5%99%A8%C2%B7%E8%A7%86%E9%A2%91%E5%85%A8%E9%A1%B5%E9%9D%A2%E6%92%AD%E6%94%BE%E9%80%82%E9%85%8D.user.js // @updateURL https://update.greasyfork.icu/scripts/527052/%E7%BD%91%E9%A1%B5%E8%A7%86%E9%A2%91%E5%80%8D%E9%80%9F%E6%92%AD%E6%94%BE%E5%99%A8%C2%B7%E8%A7%86%E9%A2%91%E5%85%A8%E9%A1%B5%E9%9D%A2%E6%92%AD%E6%94%BE%E9%80%82%E9%85%8D.meta.js // ==/UserScript== (function() { 'use strict'; class VideoFullpage { constructor() { this.handleGlobalKey = this.handleGlobalKey.bind(this); document.addEventListener('keydown', this.handleGlobalKey); this.originalStates = new Map(); } handleGlobalKey(e) { if (e.code === 'KeyH' && !e.ctrlKey && !e.altKey && !e.metaKey) { e.preventDefault(); const video = document.querySelector('video'); if (!video) return; if (video.classList.contains('video-fullpage')) { this.toggleFullpage(video); } else { if (!this.initialized) { this.createButton(); this.initialized = true; } this.toggleFullpage(video); } } } createButton() { const button = document.createElement('button'); this.button = button; button.innerHTML = '全页面'; button.style.cssText = ` position: fixed; right: 20px; top: 20px; z-index: 999999; background: rgba(0, 0, 0, 0.6); color: white; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer; font-size: 14px; transition: background-color 0.3s; display: none; `; const style = document.createElement('style'); style.textContent = ` .active { background: rgba(0, 0, 0, 0.9) !important; } `; document.head.appendChild(style); document.body.appendChild(button); button.addEventListener('click', () => { const video = document.querySelector('video'); if (video) { this.toggleFullpage(video); button.classList.toggle('active'); } }); } toggleFullpage(video) { if (video.classList.contains('video-fullpage')) { this.restoreOriginalState(video); if (this.button) { this.button.classList.remove('active'); this.button.style.display = 'none'; } } else { this.saveOriginalState(video); this.enterFullpage(video); if (this.button) { this.button.classList.add('active'); this.button.style.display = 'block'; } } } saveOriginalState(video) { const originalState = { style: video.style.cssText, parentNode: video.parentNode, nextSibling: video.nextSibling, scrollTop: window.scrollY, scrollLeft: window.scrollX, bodyOverflow: document.body.style.overflow, bodyPosition: document.body.style.position, videoPosition: { width: video.offsetWidth, height: video.offsetHeight, rect: video.getBoundingClientRect() } }; this.originalStates.set(video, originalState); } restoreOriginalState(video) { const state = this.originalStates.get(video); if (!state) return; if (this.videoEvents) { video.removeEventListener('click', this.videoEvents.click); document.removeEventListener('keydown', this.videoEvents.keydown); this.videoEvents = null; } const container = document.querySelector('.video-fullpage-container'); if (container) { if (state.parentNode) { if (state.nextSibling) { state.parentNode.insertBefore(video, state.nextSibling); } else { state.parentNode.appendChild(video); } } container.remove(); } video.classList.remove('video-fullpage'); video.style.cssText = state.style || ''; document.body.style.overflow = state.bodyOverflow || ''; document.body.style.position = state.bodyPosition || ''; requestAnimationFrame(() => { window.scrollTo({ left: state.scrollLeft || 0, top: state.scrollTop || 0, behavior: 'instant' }); }); window.removeEventListener('resize', this.resizeHandler); this.originalStates.delete(video); const hint = document.querySelector('.video-seek-hint'); if (hint) { hint.remove(); } video.style.cssText = state.style || ''; video.style.width = ''; video.style.height = ''; video.style.maxWidth = '100%'; video.style.maxHeight = '100%'; video.dispatchEvent(new CustomEvent('exitfullpage')); video.offsetHeight; if (this.progressCleanup) { this.progressCleanup(); this.progressCleanup = null; } } enterFullpage(video) { const container = document.createElement('div'); container.className = 'video-fullpage-container'; container.style.cssText = ` position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.9); z-index: 2147483646; display: flex; justify-content: center; align-items: center; `; video.classList.add('video-fullpage'); const updateVideoSize = () => { const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; const videoRatio = video.videoWidth / video.videoHeight; const windowRatio = windowWidth / windowHeight; if (windowRatio > videoRatio) { video.style.cssText = ` height: ${windowHeight}px !important; width: ${windowHeight * videoRatio}px !important; position: relative !important; z-index: 2147483647 !important; background: transparent !important; margin: 0 !important; padding: 0 !important; cursor: pointer !important; `; } else { video.style.cssText = ` width: ${windowWidth}px !important; height: ${windowWidth / videoRatio}px !important; position: relative !important; z-index: 2147483647 !important; background: transparent !important; margin: 0 !important; padding: 0 !important; cursor: pointer !important; `; } }; this.resizeHandler = updateVideoSize; window.addEventListener('resize', this.resizeHandler); const handleClick = (e) => { e.preventDefault(); e.stopPropagation(); if (video.paused) { video.play(); } else { video.pause(); } }; const handleKeydown = (e) => { if (!video.classList.contains('video-fullpage')) return; switch (e.code) { case 'Space': e.preventDefault(); if (video.paused) { video.play(); } else { video.pause(); } break; case 'ArrowLeft': e.preventDefault(); video.currentTime = Math.max(0, video.currentTime - 3); this.showSeekHint(video, '⏪ -3s'); break; case 'ArrowRight': e.preventDefault(); video.currentTime = Math.min(video.duration, video.currentTime + 3); this.showSeekHint(video, '⏩ +3s'); break; case 'ArrowUp': e.preventDefault(); video.volume = Math.min(1, video.volume + 0.1); this.showSeekHint(video, `🔊 ${Math.round(video.volume * 100)}%`); break; case 'ArrowDown': e.preventDefault(); video.volume = Math.max(0, video.volume - 0.1); this.showSeekHint(video, `🔉 ${Math.round(video.volume * 100)}%`); break; case 'KeyC': e.preventDefault(); video.playbackRate = Math.min(16, video.playbackRate + 0.1); this.showSeekHint(video, `⏩ ${video.playbackRate.toFixed(1)}x`); break; case 'KeyX': e.preventDefault(); video.playbackRate = Math.max(0.1, video.playbackRate - 0.1); this.showSeekHint(video, `⏪ ${video.playbackRate.toFixed(1)}x`); break; case 'KeyZ': e.preventDefault(); video.playbackRate = 1.0; this.showSeekHint(video, '⏮ 1.0x'); break; } }; video.addEventListener('click', handleClick); document.addEventListener('keydown', handleKeydown); this.videoEvents = { click: handleClick, keydown: handleKeydown }; container.appendChild(video); document.body.appendChild(container); document.body.style.overflow = 'hidden'; if (video.readyState >= 1) { updateVideoSize(); } else { video.addEventListener('loadedmetadata', updateVideoSize); } const progressContainer = document.createElement('div'); progressContainer.className = 'video-progress-container'; progressContainer.style.cssText = ` position: absolute; bottom: 0; left: 0; right: 0; height: 40px; background: linear-gradient(transparent, rgba(0, 0, 0, 0.7)); opacity: 0; transition: opacity 0.3s; display: flex; align-items: center; padding: 0 20px; z-index: 2147483647; `; const progress = document.createElement('div'); progress.className = 'video-progress'; progress.style.cssText = ` position: relative; width: 100%; height: 4px; background: rgba(255, 255, 255, 0.3); border-radius: 2px; cursor: pointer; `; const progressFill = document.createElement('div'); progressFill.className = 'video-progress-fill'; progressFill.style.cssText = ` position: absolute; left: 0; top: 0; height: 100%; background: #ff0000; border-radius: 2px; `; const timeDisplay = document.createElement('div'); timeDisplay.className = 'video-time-display'; timeDisplay.style.cssText = ` color: white; margin-left: 10px; font-size: 14px; min-width: 100px; text-align: right; `; progress.appendChild(progressFill); progressContainer.appendChild(progress); progressContainer.appendChild(timeDisplay); const updateProgress = () => { const percent = (video.currentTime / video.duration) * 100; progressFill.style.width = `${percent}%`; timeDisplay.textContent = `${formatTime(video.currentTime)} / ${formatTime(video.duration)}`; }; const formatTime = (seconds) => { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); if (h > 0) { return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; } return `${m}:${s.toString().padStart(2, '0')}`; }; let isDragging = false; const handleProgressClick = (e) => { const rect = progress.getBoundingClientRect(); const percent = (e.clientX - rect.left) / rect.width; video.currentTime = video.duration * percent; updateProgress(); }; progress.addEventListener('mousedown', (e) => { isDragging = true; handleProgressClick(e); }); document.addEventListener('mousemove', (e) => { if (isDragging) { handleProgressClick(e); } }); document.addEventListener('mouseup', () => { isDragging = false; }); let hideTimeout; container.addEventListener('mousemove', () => { progressContainer.style.opacity = '1'; clearTimeout(hideTimeout); hideTimeout = setTimeout(() => { if (!isDragging) { progressContainer.style.opacity = '0'; } }, 2000); }); container.addEventListener('mouseleave', () => { if (!isDragging) { progressContainer.style.opacity = '0'; } }); video.addEventListener('timeupdate', updateProgress); container.appendChild(progressContainer); const cleanup = () => { video.removeEventListener('timeupdate', updateProgress); clearTimeout(hideTimeout); }; this.progressCleanup = cleanup; } showSeekHint(video, text) { const existingHint = document.querySelector('.video-seek-hint'); if (existingHint) { existingHint.remove(); } const hint = document.createElement('div'); hint.className = 'video-seek-hint'; hint.textContent = text; hint.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.8); color: white; padding: 10px 20px; border-radius: 4px; font-size: 16px; pointer-events: none; z-index: 2147483648; animation: fadeOut 0.5s ease-out 0.5s forwards; `; const style = document.createElement('style'); style.textContent = ` @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } `; document.head.appendChild(style); const container = document.querySelector('.video-fullpage-container'); if (container) { container.appendChild(hint); setTimeout(() => hint.remove(), 1000); } } } new VideoFullpage(); })();