Greasy Fork

Greasy Fork is available in English.

Instagram视频进度条Reels 1.0.3

此脚本用于为 Instagram 网页版添加视频进度条和全局音量控制功能,提升用户的视频观看体验。建议在Reels频道使用。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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