// ==UserScript==
// @name 网页视频倍速播放器·视频全页面播放适配
// @namespace https://toolsdar.cn/
// @version 0.2
// @description 将视频播放窗口适配为全页面显示
// @author Your name
// @match *://*/*
// @grant none
// @run-at document-start
// @license MIT
// ==/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();
})();