Greasy Fork is available in English.
此脚本用于为 Instagram 网页版添加视频进度条和全局音量控制功能,提升用户的视频观看体验。建议在Reels频道使用。
// ==UserScript==
// @name Instagram视频进度条Reels 1.0.3
// @namespace http://tampermonkey.net/
// @version 1.0.3
// @description 此脚本用于为 Instagram 网页版添加视频进度条和全局音量控制功能,提升用户的视频观看体验。建议在Reels频道使用。
// @author Greasy Fork:蛋定的文弱书生
// @match *://*.instagram.com/*
// @license MIT
// ==/UserScript==
// 用户设置
var userSettings = {
defaultMuted: false, // 默认不静音为 false,默认静音为 true
defaultSeekTime: 3, // 默认快进/回退时间为 3 秒
defaultPlaybackRate: 2, // 默认加速的播放速度为 2.0
defaultVolume: 1.0, // 默认音量(0.0 到 1.0)
progressBarColor: '#ff4757' // 进度条和音量条统一颜色
}
// 全局管理器
class VideoProgressManager {
constructor(userSettings) {
this.videoControllers = new Map();
this.currentPlayingVideo = null;
this.userSettings = userSettings;
// 初始化音量,从本地存储读取或使用默认值
this.currentVolume = localStorage.getItem('instagramVideoVolume') ? parseFloat(localStorage.getItem('instagramVideoVolume')) : userSettings.defaultVolume;
this.volumeSlider = null; // 全局音量滑块
this.init();
}
init() {
this.setupMutationObserver();
this.setupIntersectionObserver();
this.handleExistingVideos();
this.setupKeyboardControl();
this.createGlobalVolumeSlider(); // 创建全局音量滑块
}
// 创建全局音量滑块
createGlobalVolumeSlider() {
// 创建音量滑块容器
this.volumeContainer = document.createElement('div');
this.volumeContainer.className = 'global-volume-container';
this.volumeContainer.style.cssText = `
position: fixed;
bottom: 50px; /* 调整为距离底部 50px */
right: 10px; /* 调整为距离右侧 10px */
width: 120px;
height: 30px;
background: rgba(0,0,0,0.7);
border-radius: 15px;
display: flex;
align-items: center;
padding: 0 10px;
z-index: 10000;
transition: opacity 0.3s ease;
`;
// 创建音量条轨道
this.volumeTrack = document.createElement('div');
this.volumeTrack.className = 'volume-track';
this.volumeTrack.style.cssText = `
flex: 1;
height: 6px;
background: rgba(255,255,255,0.3);
border-radius: 3px;
position: relative;
cursor: pointer;
`;
// 创建音量条填充
this.volumeFill = document.createElement('div');
this.volumeFill.className = 'volume-fill';
this.volumeFill.style.cssText = `
height: 100%;
background: ${this.userSettings.progressBarColor};
border-radius: 3px;
width: ${this.currentVolume * 100}%;
transition: width 0.1s ease;
`;
// 组装音量滑块
this.volumeTrack.appendChild(this.volumeFill);
this.volumeContainer.appendChild(this.volumeTrack);
document.body.appendChild(this.volumeContainer);
// 设置音量滑块事件
this.setupVolumeSliderEvents();
// 监听窗口缩放
window.addEventListener('resize', () => this.adjustVolumeSliderPosition());
// 初始化音量滑块位置
this.adjustVolumeSliderPosition();
}
// 调整音量滑块位置
adjustVolumeSliderPosition() {
if (this.volumeContainer) {
this.volumeContainer.style.right = '100px'; /* 保持距离右侧 10px */
this.volumeContainer.style.bottom = '120px'; /* 保持距离底部 50px */
}
}
// 设置音量滑块事件
setupVolumeSliderEvents() {
this.isVolumeDragging = false;
// 点击设置音量
this.volumeTrack.addEventListener('click', (e) => {
if (this.isVolumeDragging) return;
this.setVolume(e);
});
// 开始拖动
this.volumeTrack.addEventListener('mousedown', (e) => {
if (this.isVolumeDragging) return;
this.startVolumeDrag(e);
});
// 悬停显示音量预览
this.volumeTrack.addEventListener('mousemove', (e) => {
this.showVolumePreview(e);
});
// 鼠标离开隐藏预览
this.volumeTrack.addEventListener('mouseleave', () => {
this.volumeTrack.title = '';
});
}
// 设置音量
setVolume(e) {
const rect = this.volumeTrack.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const newVolume = Math.max(0, Math.min(1, clickX / rect.width));
this.currentVolume = newVolume;
this.updateAllVideosVolume(newVolume);
localStorage.setItem('instagramVideoVolume', newVolume);
this.updateVolumeSlider(newVolume);
}
// 开始拖动音量条
startVolumeDrag(e) {
this.isVolumeDragging = true;
e.preventDefault();
let lastUpdate = 0;
const throttleDelay = 16;
const rect = this.volumeTrack.getBoundingClientRect();
const handleVolumeDrag = (e) => {
const now = Date.now();
if (now - lastUpdate < throttleDelay) {
return;
}
lastUpdate = now;
requestAnimationFrame(() => {
if (!this.isVolumeDragging) return;
const dragX = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
const newVolume = Math.max(0, Math.min(1, dragX / rect.width));
this.currentVolume = newVolume;
this.updateAllVideosVolume(newVolume);
localStorage.setItem('instagramVideoVolume', newVolume);
this.updateVolumeSlider(newVolume);
});
};
const stopVolumeDrag = () => {
this.isVolumeDragging = false;
document.removeEventListener('mousemove', handleVolumeDrag);
document.removeEventListener('mouseup', stopVolumeDrag);
};
document.addEventListener('mousemove', handleVolumeDrag);
document.addEventListener('mouseup', stopVolumeDrag);
}
// 更新音量滑块显示
updateVolumeSlider(volume) {
if (this.volumeFill) {
this.volumeFill.style.width = `${volume * 100}%`;
}
}
// 显示音量预览
showVolumePreview(e) {
const rect = this.volumeTrack.getBoundingClientRect();
const hoverX = e.clientX - rect.left;
const hoverVolume = Math.max(0, Math.min(1, hoverX / rect.width));
this.volumeTrack.title = `音量: ${(hoverVolume * 100).toFixed(0)}%`;
}
// 更新所有视频的音量
updateAllVideosVolume(volume) {
this.videoControllers.forEach(controller => {
controller.video.volume = volume;
});
}
// 设置键盘控制
setupKeyboardControl() {
document.addEventListener('keydown', (e) => {
const activeElement = document.activeElement;
if (activeElement && (
activeElement.tagName === 'INPUT' ||
activeElement.tagName === 'TEXTAREA' ||
activeElement.contentEditable === 'true' ||
activeElement.isContentEditable
)) {
return;
}
switch(e.key) {
case 'ArrowLeft':
e.preventDefault();
e.stopPropagation();
this.seekVideo(-this.userSettings.defaultSeekTime); // 回退
break;
case 'ArrowRight':
e.preventDefault();
e.stopPropagation();
this.handleRightArrowDown();
break;
case ' ':
case 'Space':
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
this.togglePlayPause(); // 空格键暂停/播放
break;
case 'ArrowUp': // 音量增加
e.preventDefault();
e.stopPropagation();
this.adjustVolume(0.1);
break;
case 'ArrowDown': // 音量减少
e.preventDefault();
e.stopPropagation();
this.adjustVolume(-0.1);
break;
}
}, {
passive: false,
capture: true
});
document.addEventListener('keyup', (e) => {
if (!this.currentPlayingVideo) return;
const activeElement = document.activeElement;
if (activeElement && (
activeElement.tagName === 'INPUT' ||
activeElement.tagName === 'TEXTAREA' ||
activeElement.contentEditable === 'true' ||
activeElement.isContentEditable
)) {
return;
}
switch(e.key) {
case 'ArrowRight':
e.preventDefault();
e.stopPropagation();
this.handleRightArrowUp();
break;
case ' ':
case 'Space':
if (this.currentPlayingVideo) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
break;
}
}, {
passive: false,
capture: true
});
}
// 调整音量
adjustVolume(delta) {
const newVolume = Math.max(0, Math.min(1, this.currentVolume + delta));
this.currentVolume = newVolume;
this.updateAllVideosVolume(newVolume);
localStorage.setItem('instagramVideoVolume', newVolume);
this.updateVolumeSlider(newVolume);
}
// 处理右键按下
handleRightArrowDown() {
if (!this.currentPlayingVideo) return;
if (this.keyPressTimer) {
clearTimeout(this.keyPressTimer);
}
if(this.isLongPress === undefined) this.isLongPress = false;
this.keyPressTimer = setTimeout(() => {
this.isLongPress = true;
this.startSpeedUp();
}, 200);
}
// 处理右键释放
handleRightArrowUp() {
if (!this.currentPlayingVideo) return;
if (this.keyPressTimer) {
clearTimeout(this.keyPressTimer);
this.keyPressTimer = null;
}
if (this.isLongPress) {
this.stopSpeedUp();
} else {
this.seekVideo(this.userSettings.defaultSeekTime);
}
this.isLongPress = false;
}
// 开始倍速播放
startSpeedUp() {
if (this.currentPlayingVideo) {
this.currentPlayingVideo.playbackRate = this.userSettings.defaultPlaybackRate;
}
}
// 停止倍速播放
stopSpeedUp() {
if (this.currentPlayingVideo) {
this.currentPlayingVideo.playbackRate = 1.0;
}
}
// 快进/回退视频
seekVideo(seconds) {
if (this.currentPlayingVideo) {
const newTime = Math.max(0, Math.min(
this.currentPlayingVideo.currentTime + seconds,
this.currentPlayingVideo.duration
));
this.currentPlayingVideo.currentTime = newTime;
}
}
// 切换播放/暂停
togglePlayPause() {
if (this.currentPlayingVideo) {
if (this.currentPlayingVideo.paused) {
this.currentPlayingVideo.play();
} else {
this.currentPlayingVideo.pause();
}
}
}
// 监听DOM变化,处理新加载的视频
setupMutationObserver() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const videos = node.querySelectorAll ? node.querySelectorAll('video') : [];
videos.forEach(video => this.addVideoController(video));
if (node.tagName === 'VIDEO') {
this.addVideoController(node);
}
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// 监听视频是否在视口中
setupIntersectionObserver() {
this.intersectionObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const video = entry.target;
if (entry.isIntersecting) {
this.handleVideoInView(video);
} else {
this.handleVideoOutOfView(video);
}
});
}, {
threshold: 0.5
});
}
// 处理现有视频
handleExistingVideos() {
const videos = document.querySelectorAll('video');
videos.forEach(video => this.addVideoController(video));
}
// 为视频添加控制器
addVideoController(video) {
if (this.videoControllers.has(video)) return;
const controller = new VideoController(video, this);
this.videoControllers.set(video, controller);
video.addEventListener('play', () => {
this.setCurrentPlayingVideo(video);
});
this.intersectionObserver.observe(video);
}
// 设置当前播放的视频
setCurrentPlayingVideo(video) {
if (this.currentPlayingVideo && this.currentPlayingVideo !== video) {
this.currentPlayingVideo.pause();
}
this.currentPlayingVideo = video;
setTimeout(() => {
this.currentPlayingVideo.muted = this.userSettings.defaultMuted;
this.currentPlayingVideo.volume = this.currentVolume; // 应用保存的音量
}, 500);
}
// 视频进入视口
handleVideoInView(video) {
const controller = this.videoControllers.get(video);
if (controller) {
controller.setVisible(true);
}
}
// 视频离开视口
handleVideoOutOfView(video) {
const controller = this.videoControllers.get(video);
if (controller) {
controller.setVisible(false);
video.pause();
}
}
}
// 单个视频控制器
class VideoController {
constructor(video, manager) {
this.video = video;
this.manager = manager;
this.vElement = video.parentElement;
this.isVisible = false;
this.isDragging = false;
this.progressContainer = null;
this.init();
}
init() {
if (this.vElement.querySelector('.progress-container')) {
return;
}
this.createProgressBar();
this.setupEventListeners();
}
// 创建进度条
createProgressBar() {
// 创建进度条容器
this.progressContainer = document.createElement('div');
this.progressContainer.className = 'progress-container';
this.progressContainer.style.cssText = `
position: absolute;
bottom: 0.5%;
left: 35px;
right: 35px;
height: 20px;
background: rgba(0,0,0,0.7);
border-radius: 15px;
display: none;
align-items: center;
padding: 0 15px;
transition: opacity 0.3s ease;
z-index: 1000;
`;
// 创建时间显示
this.timeDisplay = document.createElement('span');
this.timeDisplay.className = 'time-display';
this.timeDisplay.style.cssText = `
color: white;
font-size: 12px;
margin-right: 10px;
min-width: 80px;
font-family: auto;
`;
// 创建进度条轨道
this.progressTrack = document.createElement('div');
this.progressTrack.className = 'progress-track';
this.progressTrack.style.cssText = `
flex: 1;
height: 6px;
background: rgba(255,255,255,0.3);
border-radius: 3px;
position: relative;
cursor: pointer;
`;
// 创建进度条填充
this.progressFill = document.createElement('div');
this.progressFill.className = 'progress-fill';
this.progressFill.style.cssText = `
height: 100%;
background: ${this.manager.userSettings.progressBarColor};
border-radius: 3px;
width: 0%;
transition: width 0.1s ease;
`;
// 组装进度条
this.progressTrack.appendChild(this.progressFill);
this.progressContainer.appendChild(this.timeDisplay);
this.progressContainer.appendChild(this.progressTrack);
// 确保父元素有相对定位
this.vElement.style.position = 'relative';
this.vElement.appendChild(this.progressContainer);
// 监听窗口缩放以调整位置
window.addEventListener('resize', () => this.adjustProgressBarPosition());
}
// 调整进度条位置
adjustProgressBarPosition() {
if (this.progressContainer) {
const parentRect = this.vElement.getBoundingClientRect();
this.progressContainer.style.left = '30px';
this.progressContainer.style.right = '30px';
this.progressContainer.style.bottom = '0.5%';
this.progressContainer.style.height = '20px';
}
}
// 设置事件监听器
setupEventListeners() {
// 鼠标进入显示进度条
this.vElement.addEventListener("mouseenter", () => {
if (this.isVisible) {
this.showProgressBar();
}
});
// 鼠标离开隐藏进度条
this.vElement.addEventListener("mouseleave", () => {
if (!this.isDragging) {
this.hideProgressBar();
}
});
// 点击进度条跳转
this.progressTrack.addEventListener('click', (e) => {
if (this.isDragging) return;
this.seekToPosition(e);
});
// 开始拖动进度条
this.progressTrack.addEventListener('mousedown', (e) => {
if (this.isDragging) return;
this.startDrag(e);
});
// 悬停显示预览时间
this.progressTrack.addEventListener('mousemove', (e) => {
this.showPreviewTime(e);
});
// 监听视频时间更新
this.video.addEventListener('timeupdate', () => {
this.updateProgress();
});
// 监听视频元数据加载完成
this.video.addEventListener('loadedmetadata', () => {
this.updateProgress();
});
// 移除播放时自动显示进度条的逻辑
// this.video.addEventListener('play', () => {
// this.showProgressBar();
// });
// 暂停时不自动隐藏进度条(依赖鼠标事件)
this.video.addEventListener('pause', () => {
// 保持现有状态,由鼠标事件控制
});
}
// 显示进度条
showProgressBar() {
if (this.progressContainer) {
this.progressContainer.style.display = 'flex';
}
}
// 隐藏进度条
hideProgressBar() {
if (this.progressContainer && !this.isDragging) {
this.progressContainer.style.display = 'none';
}
}
// 更新进度条
updateProgress() {
if (this.video.duration && this.progressFill && this.timeDisplay) {
const progress = (this.video.currentTime / this.video.duration) * 100;
this.progressFill.style.width = progress + '%';
const currentTime = this.formatTime(this.video.currentTime);
const duration = this.formatTime(this.video.duration);
this.timeDisplay.textContent = `${currentTime} / ${duration}`;
}
}
// 跳转到指定位置
seekToPosition(e) {
const rect = this.progressTrack.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const newTime = (clickX / rect.width) * this.video.duration;
this.video.currentTime = newTime;
}
// 开始拖动进度条
startDrag(e) {
this.isDragging = true;
e.preventDefault();
if (this.originMutedState === undefined) {
this.originMutedState = this.video.muted;
}
this.video.muted = true;
if (this.originPlayedState === undefined) {
this.originPlayedState = this.video.paused;
}
this.video.pause();
let lastUpdate = 0;
const throttleDelay = 16;
const rect = this.progressTrack.getBoundingClientRect();
const handleDrag = (e) => {
const now = Date.now();
if (now - lastUpdate < throttleDelay) {
return;
}
lastUpdate = now;
requestAnimationFrame(() => {
if (!this.isDragging) return;
const dragX = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
const newTime = (dragX / rect.width) * this.video.duration;
if (Math.abs(this.video.currentTime - newTime) > 0.1) {
this.video.currentTime = newTime;
}
const progress = (newTime / this.video.duration) * 100;
this.progressFill.style.width = progress + '%';
const currentTime = this.formatTime(newTime);
const duration = this.formatTime(this.video.duration);
this.timeDisplay.textContent = `${currentTime} / ${duration}`;
});
};
const stopDrag = () => {
this.isDragging = false;
setTimeout(() => {
if (this.originMutedState !== undefined) {
this.video.muted = this.originMutedState;
this.originMutedState = undefined;
}
if (this.originPlayedState !== undefined) {
if (!this.originPlayedState) {
this.video.play();
} else {
this.video.pause();
}
this.originPlayedState = undefined;
}
}, 100);
document.removeEventListener('mousemove', handleDrag);
document.removeEventListener('mouseup', stopDrag);
this.updateProgress();
};
document.addEventListener('mousemove', handleDrag);
document.addEventListener('mouseup', stopDrag);
}
// 显示进度预览时间
showPreviewTime(e) {
const rect = this.progressTrack.getBoundingClientRect();
const hoverX = e.clientX - rect.left;
const hoverTime = (hoverX / rect.width) * this.video.duration;
this.progressTrack.title = this.formatTime(hoverTime);
}
// 格式化时间
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
// 设置可见性
setVisible(visible) {
this.isVisible = visible;
if (!visible) {
this.hideProgressBar();
}
}
// 销毁控制器
destroy() {
if (this.progressContainer) {
this.progressContainer.remove();
}
}
}
// 初始化管理器
let videoManager;
function initializeVideoManager() {
if (!videoManager) {
videoManager = new VideoProgressManager(userSettings);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeVideoManager);
} else {
initializeVideoManager();
}