Instagram Video Controls

Add better video controls to Instagram videos

当前为 2025-01-15 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Instagram Video Controls
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Add better video controls to Instagram videos
// @match        https://www.instagram.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const createControls = (videoElement) => {
        // Main container
        const container = document.createElement('div');
        container.style.cssText = `
            width: 100%;
            height: 48px;
            background: rgba(0,0,0,0.8);
            display: flex;
            flex-direction: column;
            z-index: 9999999;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            position: relative;
            pointer-events: all !important;
        `;

        // Timeline container
        const timelineContainer = document.createElement('div');
        timelineContainer.style.cssText = `
            width: 100%;
            height: 20px;
            position: relative;
            cursor: pointer;
            padding: 8px 0;
            z-index: 9999999;
            pointer-events: all !important;
        `;

        // Timeline bar
        const timeline = document.createElement('div');
        timeline.style.cssText = `
            width: 100%;
            height: 3px;
            background: rgba(255,255,255,0.2);
            position: relative;
            transition: height 0.1s;
            z-index: 9999999;
        `;

        // Progress bar
        const progress = document.createElement('div');
        progress.style.cssText = `
            height: 100%;
            background: #0095f6;
            width: 0%;
            position: absolute;
            top: 0;
            left: 0;
            z-index: 9999999;
        `;

        // Seek handle
        const seekHandle = document.createElement('div');
        seekHandle.style.cssText = `
            width: 12px;
            height: 12px;
            background: #0095f6;
            border-radius: 50%;
            position: absolute;
            right: -6px;
            top: 50%;
            transform: translateY(-50%) scale(0);
            transition: transform 0.1s;
            z-index: 10000000;
        `;
        progress.appendChild(seekHandle);

        // Time tooltip
        const timeTooltip = document.createElement('div');
        timeTooltip.style.cssText = `
            position: absolute;
            background: rgba(0,0,0,0.8);
            color: white;
            padding: 4px 8px;
            border-radius: 4px;
            font-size: 12px;
            bottom: 100%;
            transform: translateX(-50%);
            display: none;
            z-index: 10000000;
            pointer-events: none;
            white-space: nowrap;
            margin-bottom: 8px;
        `;

        // Controls row
        const controlsRow = document.createElement('div');
        controlsRow.style.cssText = `
            display: flex;
            align-items: center;
            padding: 0 12px;
            height: 28px;
            position: relative;
            z-index: 9999999;
            pointer-events: all !important;
        `;

        // Play button
        const playButton = document.createElement('button');
        playButton.innerHTML = '⏵️';
        playButton.style.cssText = `
            background: none;
            border: none;
            color: white;
            font-size: 24px;
            cursor: pointer;
            padding: 0;
            margin-right: 12px;
            position: relative;
            z-index: 10000000;
            pointer-events: all !important;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 32px;
            height: 32px;
        `;

        // Time display
        const timeDisplay = document.createElement('span');
        timeDisplay.style.cssText = `
            color: white;
            font-size: 13px;
            margin-right: 12px;
            font-family: monospace;
            position: relative;
            z-index: 10000000;
        `;

        // Volume control
        const volumeControl = document.createElement('div');
        volumeControl.style.cssText = `
            display: flex;
            align-items: center;
            position: relative;
            z-index: 10000000;
            pointer-events: all !important;
        `;

        const volumeButton = document.createElement('button');
        volumeButton.innerHTML = '🔊';
        volumeButton.style.cssText = `
            background: none;
            border: none;
            color: white;
            font-size: 20px;
            cursor: pointer;
            padding: 0;
            margin-right: 8px;
            position: relative;
            z-index: 10000000;
            pointer-events: all !important;
            width: 32px;
            height: 32px;
            display: flex;
            align-items: center;
            justify-content: center;
        `;

        // Assemble the controls
        timeline.appendChild(progress);
        timelineContainer.appendChild(timeline);
        timelineContainer.appendChild(timeTooltip);

        controlsRow.appendChild(playButton);
        controlsRow.appendChild(timeDisplay);
        controlsRow.appendChild(volumeButton);

        container.appendChild(timelineContainer);
        container.appendChild(controlsRow);

        // Helper function for time formatting
        const formatTime = (seconds) => {
            const mins = Math.floor(seconds / 60);
            const secs = Math.floor(seconds % 60);
            return `${mins}:${secs.toString().padStart(2, '0')}`;
        };

        // Track dragging state
        let isDragging = false;

        // Show timeline preview on hover
        timelineContainer.addEventListener('mousemove', (e) => {
            const rect = timeline.getBoundingClientRect();
            const pos = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));

            if (!isDragging) {
                timeTooltip.style.display = 'block';
                timeTooltip.style.left = `${pos * 100}%`;
                const previewTime = videoElement.duration * pos;
                timeTooltip.textContent = formatTime(previewTime);
            }
        });

        // Handle dragging
        const handleDrag = (e) => {
            if (!isDragging) return;

            const rect = timeline.getBoundingClientRect();
            const pos = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
            const newTime = videoElement.duration * pos;

            progress.style.width = `${pos * 100}%`;
            timeTooltip.style.left = `${pos * 100}%`;
            timeTooltip.textContent = formatTime(newTime);
            videoElement.currentTime = newTime;
        };

        // Start dragging
        timelineContainer.addEventListener('mousedown', (e) => {
            e.stopPropagation();
            isDragging = true;
            timeline.style.height = '5px';
            seekHandle.style.transform = 'translateY(-50%) scale(1)';
            handleDrag(e);

            // Add document-level event listeners
            document.addEventListener('mousemove', handleDrag);
            document.addEventListener('mouseup', () => {
                isDragging = false;
                timeline.style.height = '3px';
                seekHandle.style.transform = 'translateY(-50%) scale(0)';
                document.removeEventListener('mousemove', handleDrag);
            }, { once: true });
        });

        // Hide preview when leaving timeline
        timelineContainer.addEventListener('mouseleave', () => {
            if (!isDragging) {
                timeTooltip.style.display = 'none';
                timeline.style.height = '3px';
                seekHandle.style.transform = 'translateY(-50%) scale(0)';
            }
        });

        timelineContainer.addEventListener('mouseenter', () => {
            if (!isDragging) {
                timeline.style.height = '5px';
                seekHandle.style.transform = 'translateY(-50%) scale(1)';
            }
        });

        // Play/Pause handler
        playButton.addEventListener('click', (e) => {
            e.stopPropagation();
            if (videoElement.paused) {
                videoElement.play();
                playButton.innerHTML = '⏸️';
            } else {
                videoElement.pause();
                playButton.innerHTML = '⏵️';
            }
        });

        // Volume handler
        volumeButton.addEventListener('click', (e) => {
            e.stopPropagation();
            videoElement.muted = !videoElement.muted;
            volumeButton.innerHTML = videoElement.muted ? '🔇' : '🔊';
        });

        // Update progress
        const updateProgress = () => {
            if (!isDragging) {
                const progress_value = (videoElement.currentTime / videoElement.duration) * 100;
                progress.style.width = `${progress_value}%`;
                timeDisplay.textContent = `${formatTime(videoElement.currentTime)} / ${formatTime(videoElement.duration)}`;
            }
        };

        videoElement.addEventListener('timeupdate', updateProgress);
        videoElement.addEventListener('loadedmetadata', updateProgress);
        videoElement.addEventListener('play', () => playButton.innerHTML = '⏸️');
        videoElement.addEventListener('pause', () => playButton.innerHTML = '⏵️');

        return container;
    };

    const addControlsToVideo = (videoElement) => {
        if (videoElement.dataset.customControls) return;
        videoElement.dataset.customControls = 'true';

        const videoContainer = videoElement.closest('div[class*="x5yr21d"][class*="x1uhb9sk"]');
        if (!videoContainer) return;

        const controlsWrapper = document.createElement('div');
        controlsWrapper.style.cssText = `
            width: 100%;
            position: relative;
            z-index: 9999999;
            pointer-events: all !important;
        `;

        const controls = createControls(videoElement);
        controlsWrapper.appendChild(controls);

        videoContainer.parentElement.insertBefore(controlsWrapper, videoContainer);
        videoContainer.style.position = 'relative';
        videoContainer.style.zIndex = '1';
    };

    // Observer to watch for new videos
    const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                if (node.nodeName === 'VIDEO') {
                    addControlsToVideo(node);
                } else if (node.querySelectorAll) {
                    const videos = node.querySelectorAll('video');
                    videos.forEach(addControlsToVideo);
                }
            }
        }
    });

    // Start observing
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // Process existing videos
    document.querySelectorAll('video').forEach(addControlsToVideo);
})();