前言 #
在实际观看网页视频时,我们经常需要调整视频播放速度或添加循环播放功能。本文将介绍如何在Edge浏览器中安装油猴插件(Tampermonkey),并使用自定义脚本来实现视频控制功能。
安装油猴插件 #
开启开发者模式 #
- 打开Edge浏览器
- 点击右上角的"…“菜单
- 选择"扩展"选项
- 在扩展页面左侧找到并打开"开发人员模式"开关
安装Tampermonkey #
- 下载Tampermonkey插件文件点击下载Tampermonkey
- 将下载好的.crx文件直接拖拽到Edge浏览器的扩展页面
- 在弹出的安装确认对话框中点击"添加扩展”
- 安装完成后,浏览器右上角会出现Tampermonkey图标
添加视频控制脚本 #
创建新脚本 #
- 点击浏览器右上角的Tampermonkey图标
- 选择"创建新脚本"
- 在编辑器中粘贴以下代码:
// ==UserScript==
// @name 小杜视频控速助手(油猴版)
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 支持HTML5视频加速播放,支持任意倍速
// @author 小杜
// @match *://*/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// 添加一个检测视频/音频的函数
function checkMediaElements() {
const videos = document.getElementsByTagName('video');
const audios = document.getElementsByTagName('audio');
return videos.length > 0 || audios.length > 0;
}
// 创建或显示控制面板
function showControlPanel() {
let panel = document.getElementById('speed-control-tampermonkey');
if (!panel) {
createControlPanel();
} else {
panel.style.display = 'block';
}
}
// 隐藏控制面板
function hideControlPanel() {
const panel = document.getElementById('speed-control-tampermonkey');
if (panel) {
panel.style.display = 'none';
}
}
// 创建控制面板UI
function createControlPanel() {
// 避免重复创建
if (document.getElementById('speed-control-tampermonkey')) {
return;
}
const panel = document.createElement('div');
panel.innerHTML = `
<div id="speed-control-tampermonkey" style="
position: fixed;
top: ${panelPosition.top};
right: ${panelPosition.right};
background: #1a1a1a;
padding: 12px;
border-radius: 8px;
z-index: 999999;
color: white;
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
width: 180px;
transition: all 0.3s;
cursor: move;
">
<!-- 标题栏 -->
<div style="
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
">
<span style="font-size: 14px; color: #888;">视频控速助手</span>
<button id="toggle-mini" style="
padding: 2px 6px;
border: none;
border-radius: 3px;
background: #333;
color: #888;
font-size: 12px;
cursor: pointer;
">迷你模式</button>
</div>
<!-- 速度显示 -->
<div style="
text-align: center;
margin: 8px 0;
font-size: 24px;
font-weight: 600;
color: #00ff9d;
font-family: 'Monaco', monospace;
">
<span id="speed-display">${currentSpeed.toFixed(2)}x</span>
</div>
<!-- 滑块控制 -->
<input type="range" id="speed-slider"
min="0.5"
max="16"
step="0.5"
value="${currentSpeed}"
style="
width: 100%;
height: 4px;
-webkit-appearance: none;
background: #333;
border-radius: 2px;
margin: 12px 0;
"
>
<!-- 预设速度按钮 -->
<div style="
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 4px;
margin: 8px 0;
">
${presetSpeeds.map(speed => `
<button class="preset-speed" data-speed="${speed}" style="
padding: 4px 0;
border: none;
border-radius: 3px;
background: #333;
color: #888;
font-size: 12px;
cursor: pointer;
">${speed}x</button>
`).join('')}
</div>
<!-- 自定义速度 -->
<div style="display: flex; gap: 4px; margin: 8px 0;">
<input type="number" id="custom-speed"
style="
flex: 1;
padding: 4px;
border: none;
border-radius: 3px;
background: #333;
color: white;
font-size: 12px;
text-align: center;
"
step="0.5"
min="0.5"
max="16"
value="${currentSpeed}"
>
<button id="set-speed" style="
padding: 4px 8px;
border: none;
border-radius: 3px;
background: #00ff9d;
color: #000;
font-size: 12px;
cursor: pointer;
">设置</button>
</div>
<!-- 功能区 -->
<div style="
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
margin: 8px 0;
">
<button id="pip-mode" class="function-btn">画中画</button>
<button id="screenshot" class="function-btn">截图</button>
<button id="manage-presets" class="function-btn">预设</button>
</div>
<!-- 开关区 -->
<div style="
display: flex;
justify-content: space-between;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #333;
">
<label style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="enable-control" checked style="accent-color: #00ff9d;">
<span style="font-size: 12px; color: #888;">启用控速</span>
</label>
<label style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="enable-fake-time" style="accent-color: #00ff9d;">
<span style="font-size: 12px; color: #888;">模拟进度</span>
</label>
</div>
<!-- 快捷键提示 -->
<div style="
font-size: 11px;
color: #666;
text-align: center;
margin-top: 8px;
">
快捷键: [+]加速 [-]减速 [R]重置
</div>
</div>
`;
document.body.appendChild(panel);
// 获取控制元素
const controlPanel = document.getElementById('speed-control-tampermonkey');
const enableControl = document.getElementById('enable-control');
const speedSlider = document.getElementById('speed-slider');
const speedDisplay = document.getElementById('speed-display');
const customSpeed = document.getElementById('custom-speed');
const setSpeedBtn = document.getElementById('set-speed');
const enableFakeTime = document.getElementById('enable-fake-time');
const pipButton = document.getElementById('pip-mode');
const screenshotButton = document.getElementById('screenshot');
const managePresetsButton = document.getElementById('manage-presets');
const miniModeButton = document.getElementById('toggle-mini');
// 添加拖拽支持
addDragSupport(controlPanel);
// 绑定事件
enableControl.addEventListener('change', function() {
isEnabled = this.checked;
if (isEnabled) {
setVideoSpeed(currentSpeed);
} else {
const videos = document.getElementsByTagName('video');
for (let video of videos) {
if (video._speedProtected) {
delete video.playbackRate;
video._speedProtected = false;
}
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate')
.set.call(video, 1.0);
}
}
});
speedSlider.addEventListener('input', function() {
currentSpeed = parseFloat(this.value);
updateSpeed(currentSpeed);
});
setSpeedBtn.addEventListener('click', function() {
const speed = parseFloat(customSpeed.value);
if(speed >= 0.5 && speed <= 16) {
currentSpeed = speed;
updateSpeed(speed);
}
});
// 绑定预设速度按钮事件
document.querySelectorAll('.preset-speed').forEach(btn => {
btn.addEventListener('click', function() {
const speed = parseFloat(this.dataset.speed);
currentSpeed = speed;
updateSpeed(speed);
});
});
// 绑定功能按钮事件
pipButton.addEventListener('click', togglePiP);
screenshotButton.addEventListener('click', takeScreenshot);
managePresetsButton.addEventListener('click', managePresets);
miniModeButton.addEventListener('click', toggleMiniMode);
enableFakeTime.addEventListener('change', function() {
isFaking = this.checked;
const videos = document.getElementsByTagName('video');
for (let video of videos) {
if (isFaking) {
enableFakeProgress(video);
} else {
if (video._fakeProgressEnabled) {
delete video.currentTime;
video._fakeProgressEnabled = false;
if (originalTimeUpdate) {
video.ontimeupdate = originalTimeUpdate;
}
}
}
}
});
// 恢复上次的迷你模式状态
const wasMini = localStorage.getItem('miniMode') === 'true';
if (wasMini) {
toggleMiniMode();
}
// 在createControlPanel函数中添加样式部分
const style = document.createElement('style');
style.textContent = `
#speed-control-tampermonkey input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
border-radius: 50%;
background: #00ff9d;
cursor: pointer;
border: none;
}
#speed-control-tampermonkey input[type="range"]::-webkit-slider-runnable-track {
background: #333;
height: 4px;
border-radius: 2px;
}
.function-btn {
padding: 4px 0;
border: none;
border-radius: 3px;
background: #333;
color: #888;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
.function-btn:hover,
.preset-speed:hover,
#toggle-mini:hover {
background: #444;
color: #fff;
}
#speed-control-tampermonkey.mini-mode {
width: 120px;
}
#speed-control-tampermonkey.mini-mode .hide-in-mini {
display: none;
}
`;
document.head.appendChild(style);
// 添加迷你模式的样式
style.textContent += `
#speed-control-tampermonkey.mini-mode {
padding: 12px;
}
#speed-control-tampermonkey.mini-mode #speed-display {
font-size: 18px;
}
#speed-control-tampermonkey.mini-mode #speed-slider {
margin: 8px 0;
}
`;
}
// 保存当前速度的全局变量
let currentSpeed = parseFloat(localStorage.getItem('lastSpeed')) || 1.0;
let isFaking = false;
let animationFrameId = null;
let isEnabled = true;
let observerTimeout;
let panelPosition = JSON.parse(localStorage.getItem('speedControlPosition')) || { top: '160px', right: '20px' };
let presetSpeeds = JSON.parse(localStorage.getItem('speedPresets')) || [2, 4, 8, 16];
let originalTimeUpdate = null;
let fakeCurrentTime = 0;
let lastRealTime = 0;
// 设置视频速度的函数
function setVideoSpeed(speed) {
const videos = document.getElementsByTagName('video');
for (let video of videos) {
try {
if (!isEnabled) {
if (video._speedProtected) {
delete video.playbackRate;
video._speedProtected = false;
}
return;
}
// 启用时间模拟
if (isFaking) {
enableFakeProgress(video);
}
video.playbackRate = speed;
if (video._speedProtected) {
delete video.playbackRate;
video._speedProtected = false;
}
Object.defineProperty(video, 'playbackRate', {
get: function() {
return speed;
},
set: function(newValue) {
if (isEnabled) {
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate')
.set.call(this, speed);
} else {
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate')
.set.call(this, newValue);
}
},
configurable: true
});
video._speedProtected = true;
} catch(e) {
console.error('设置视频速度失败:', e);
}
}
}
// 添加视频事件监听
function addVideoEventListeners(video) {
if(video._eventsAdded) return;
video._eventsAdded = true;
video.addEventListener('ratechange', function(e) {
if(isEnabled && this.playbackRate !== currentSpeed) {
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate')
.set.call(this, currentSpeed);
}
});
video.addEventListener('play', function() {
if(isEnabled) {
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate')
.set.call(this, currentSpeed);
}
});
}
// 监听新加入的视频
const observer = new MutationObserver(function(mutations) {
clearTimeout(observerTimeout);
observerTimeout = setTimeout(() => {
if (checkMediaElements()) {
showControlPanel();
const videos = document.getElementsByTagName('video');
for(let video of videos) {
if(!video._eventsAdded) {
addVideoEventListeners(video);
}
if(video.playbackRate !== currentSpeed && isEnabled) {
setVideoSpeed(currentSpeed);
}
}
} else {
hideControlPanel();
}
}, 200);
});
// 开始观察DOM变化
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
// 修改页面加载事件监听
let panelCreated = false; // 添加标志位
document.addEventListener('DOMContentLoaded', function() {
if (!panelCreated && checkMediaElements()) {
createControlPanel();
panelCreated = true;
const videos = document.getElementsByTagName('video');
for(let video of videos) {
addVideoEventListeners(video);
if(isEnabled) {
setVideoSpeed(currentSpeed);
}
}
}
});
function addDragSupport(panel) {
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
panel.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
function dragStart(e) {
initialX = e.clientX - panel.offsetLeft;
initialY = e.clientY - panel.offsetTop;
if (e.target === panel) {
isDragging = true;
}
}
function drag(e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
// 更新位置
panel.style.top = currentY + 'px';
panel.style.right = (window.innerWidth - currentX - panel.offsetWidth) + 'px';
// 保存位置到localStorage
panelPosition = {
top: panel.style.top,
right: panel.style.right
};
localStorage.setItem('speedControlPosition', JSON.stringify(panelPosition));
}
}
function dragEnd() {
isDragging = false;
}
}
document.addEventListener('keydown', function(e) {
if (!isEnabled) return;
switch(e.key) {
case '+':
case '=':
e.preventDefault();
currentSpeed = Math.min(currentSpeed + 0.5, 16);
updateSpeed(currentSpeed);
break;
case '-':
e.preventDefault();
currentSpeed = Math.max(currentSpeed - 0.5, 0.5);
updateSpeed(currentSpeed);
break;
case 'r':
case 'R':
e.preventDefault();
currentSpeed = 1.0;
updateSpeed(currentSpeed);
break;
}
});
function updateSpeed(speed) {
const speedDisplay = document.getElementById('speed-display');
const speedSlider = document.getElementById('speed-slider');
const customSpeed = document.getElementById('custom-speed');
if (speedDisplay) speedDisplay.textContent = speed.toFixed(2) + 'x';
if (speedSlider) speedSlider.value = speed;
if (customSpeed) customSpeed.value = speed;
setVideoSpeed(speed);
localStorage.setItem('lastSpeed', speed);
}
// 添加模拟播放时间的函数
function enableFakeProgress(video) {
if (video._fakeProgressEnabled) return;
video._fakeProgressEnabled = true;
// 保存原始的currentTime
let realCurrentTime = video.currentTime;
fakeCurrentTime = realCurrentTime;
lastRealTime = Date.now();
// 保存原始的timeupdate事件
originalTimeUpdate = video.ontimeupdate;
// 重写currentTime的getter和setter
Object.defineProperty(video, 'currentTime', {
get: function() {
if (!isEnabled || !isFaking) return realCurrentTime;
return fakeCurrentTime;
},
set: function(value) {
realCurrentTime = value;
fakeCurrentTime = value;
lastRealTime = Date.now();
},
configurable: true
});
// 模拟timeupdate事件
function fakeTimeUpdate() {
if (isEnabled && isFaking && !video.paused) {
const now = Date.now();
const deltaTime = (now - lastRealTime) / 1000;
lastRealTime = now;
fakeCurrentTime += deltaTime * currentSpeed;
// 触发timeupdate事件
const event = new Event('timeupdate');
video.dispatchEvent(event);
// 如果有原始的timeupdate处理函数,调用它
if (originalTimeUpdate) {
originalTimeUpdate.call(video);
}
}
// 继续下一帧
animationFrameId = requestAnimationFrame(fakeTimeUpdate);
}
// 开始模拟
fakeTimeUpdate();
}
// 修改预设管理功能
function managePresets() {
const savedPresets = JSON.parse(localStorage.getItem('speedPresets')) || presetSpeeds;
const newPreset = prompt('请输入新的预设速度(多个请用逗号分隔):', savedPresets.join(','));
if (newPreset) {
const presets = newPreset.split(',')
.map(x => parseFloat(x))
.filter(x => !isNaN(x) && x >= 0.5 && x <= 16)
.sort((a, b) => a - b);
if (presets.length > 0) {
localStorage.setItem('speedPresets', JSON.stringify(presets));
presetSpeeds = presets; // 更新当前预设
// 更新预设按钮
const presetContainer = document.querySelector('#speed-control-tampermonkey .preset-speed').parentElement;
presetContainer.innerHTML = presets.map(speed => `
<button class="preset-speed" data-speed="${speed}" style="
padding: 4px 0;
border: none;
border-radius: 3px;
background: #333;
color: #888;
font-size: 12px;
cursor: pointer;
">${speed}x</button>
`).join('');
// 重新绑定预设按钮事件
document.querySelectorAll('.preset-speed').forEach(btn => {
btn.addEventListener('click', function() {
const speed = parseFloat(this.dataset.speed);
currentSpeed = speed;
updateSpeed(speed);
});
});
}
}
}
// 修改迷你模式切换功能
function toggleMiniMode() {
const panel = document.getElementById('speed-control-tampermonkey');
const isMini = panel.classList.toggle('mini-mode');
localStorage.setItem('miniMode', isMini);
if (isMini) {
// 隐藏不需要的元素
panel.querySelectorAll('.function-btn, #manage-presets, #custom-speed, #set-speed').forEach(el => {
el.style.display = 'none';
});
// 调整面板宽度
panel.style.width = '120px';
} else {
// 显示所有元素
panel.querySelectorAll('.function-btn, #manage-presets, #custom-speed, #set-speed').forEach(el => {
el.style.display = '';
});
// 恢复面板宽度
panel.style.width = '180px';
}
}
// 添加画中画功能
async function togglePiP() {
const videos = document.getElementsByTagName('video');
if (videos.length > 0) {
try {
if (document.pictureInPictureElement) {
await document.exitPictureInPicture();
} else {
await videos[0].requestPictureInPicture();
}
} catch (error) {
console.error('画中画模式切换失败:', error);
}
}
}
// 添加截图功能
function takeScreenshot() {
const videos = document.getElementsByTagName('video');
if (videos.length > 0) {
const video = videos[0];
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0);
const link = document.createElement('a');
link.download = `截图_${new Date().getTime()}.png`;
link.href = canvas.toDataURL();
link.click();
}
}
// 添加循环播放功能
function toggleLoop(enabled) {
const videos = document.getElementsByTagName('video');
for (let video of videos) {
video.loop = enabled;
}
}
})();
保存并启用脚本 #
- 点击编辑器左上角的"文件"→“保存”,或使用快捷键Ctrl+S
- 确保脚本在Tampermonkey的管理面板中处于启用状态
使用说明 #
安装完成后,当你访问包含视频的网页时,会在页面右上角出现控制面板,包含以下功能:
-
循环播放开关
- 点击按钮切换循环播放状态
- 开启后视频将自动循环播放
-
倍速播放选择
- 提供0.5x到16.0x的播放速度选项
- 选择后立即生效
注意事项 #
-
脚本兼容性
- 部分网站可能因为安全限制而无法使用该脚本
- 如遇到不兼容情况,请检查网站是否允许脚本注入
-
性能考虑
- 脚本会定期检查页面中的视频元素
- 如果页面性能出现问题,可以尝试禁用脚本
-
使用建议
- 建议根据实际需求选择合适的播放速度
- 不建议长时间使用过快的播放速度
总结 #
通过安装Tampermonkey插件并使用自定义脚本,我们可以方便地控制网页视频的播放方式。这个工具不仅提高了观看视频的效率,还为用户提供了更好的视频控制体验。希望这个指南能帮助你更好地使用网页视频控速工具。