Instagram Custom Video Controls

Custom video controls on Instagram: play/pause, progress, volume, mute, speed, fullscreen, blur effect. Works on all videos.

// ==UserScript==
// @name         Instagram Custom Video Controls
// @name:ru      Instagram — Кастомные видеоконтролы
// @namespace    https://greasyfork.org/ru/users/1384659
// @version      1.0
// @description  Custom video controls on Instagram: play/pause, progress, volume, mute, speed, fullscreen, blur effect. Works on all videos.
// @description:ru  Заменяет стандартные контролы Instagram на кастомную панель (пауза/старт, громкость, скорость, fullscreen, размытый фон).
// @author       FerNikoMF
// @match        https://www.instagram.com/*
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let isMuted = false;
    let lastVolume = 1;
    let styleAdded = false;

    function initPlayer(video) {
        if (video.hasCustomControls) return;
        video.hasCustomControls = true;

        video.removeAttribute("controls");

        const panel = document.createElement("div");
        panel.className = "custom-controls";
        panel.innerHTML = `
            <button class="play-pause">▶️</button>
            <input type="range" class="progress" min="0" max="100" value="0">
            <span class="time">0:00 / 0:00</span>
            <div class="volume-wrapper">
                <button class="mute">🔊</button>
                <input type="range" class="volume-slider" min="0" max="1" step="0.01" value="${lastVolume}">
            </div>
            <select class="speed">
                <option value="0.5">0.5x</option>
                <option value="1" selected>1x</option>
                <option value="1.5">1.5x</option>
                <option value="2">2x</option>
            </select>
            <button class="fullscreen">⛶</button>
        `;

        if (!styleAdded) {
            const style = document.createElement("style");
            style.textContent = `
                .custom-controls {
                    position: absolute;
                    bottom: 0;
                    left: 0;
                    right: 0;
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
                    background: rgba(0,0,0,0.4);
                    backdrop-filter: blur(8px);
                    padding: 5px;
                    opacity: 0;
                    transition: opacity 0.3s;
                    z-index: 9999;
                }
                .progress { flex: 1; margin: 0 5px; }
                .time { font-size: 12px; color: white; margin-right: 10px; }
                .volume-wrapper { position: relative; display: inline-block; }
                .volume-slider {
                    display: none;
                    position: absolute;
                    bottom: 30px;
                    left: 50%;
                    transform: translateX(-50%);
                    height: 80px;
                    width: 4px;
                    writing-mode: bt-lr;
                    -webkit-appearance: slider-vertical;
                }
                .volume-wrapper:hover .volume-slider { display: block; }
                select.speed {
                    margin: 0 5px;
                    background: black;
                    color: white;
                    border: none;
                    border-radius: 3px;
                    padding: 2px;
                }
                button {
                    background: none;
                    border: none;
                    color: white;
                    font-size: 16px;
                    cursor: pointer;
                }
            `;
            document.head.appendChild(style);
            styleAdded = true;
        }

        video.parentElement.style.position = "relative";
        video.parentElement.appendChild(panel);

        const playPause = panel.querySelector(".play-pause");
        const progress = panel.querySelector(".progress");
        const time = panel.querySelector(".time");
        const muteBtn = panel.querySelector(".mute");
        const volumeSlider = panel.querySelector(".volume-slider");
        const speed = panel.querySelector(".speed");
        const fullscreenBtn = panel.querySelector(".fullscreen");

        // === Контролы показываются при движении мыши ===
        let hideTimer;
        function showPanel() {
            panel.style.opacity = "1";
            clearTimeout(hideTimer);
            hideTimer = setTimeout(() => {
                panel.style.opacity = "0";
            }, 2000);
        }
        video.addEventListener("mousemove", showPanel);
        panel.addEventListener("mousemove", showPanel);

        // === Play/Pause ===
        playPause.onclick = () => {
            if (video.paused) {
                video.play();
                playPause.textContent = "⏸";
            } else {
                video.pause();
                playPause.textContent = "▶️";
            }
        };

        // === Прогресс ===
        video.addEventListener("timeupdate", () => {
            progress.value = (video.currentTime / video.duration) * 100 || 0;
            time.textContent = format(video.currentTime) + " / " + format(video.duration);
        });

        progress.oninput = () => {
            video.currentTime = (progress.value / 100) * video.duration;
        };

        // === Mute/Volume ===
        muteBtn.onclick = () => {
            isMuted = !isMuted;
            applyGlobalSettings(video, muteBtn);
        };

        volumeSlider.oninput = () => {
            lastVolume = video.volume = volumeSlider.value;
            isMuted = video.volume === 0;
            applyGlobalSettings(video, muteBtn);
        };

        // === Синхронизация глобальных настроек ===
        video.addEventListener("volumechange", () => {
            if (video.muted !== isMuted || video.volume !== lastVolume) {
                video.muted = isMuted;
                video.volume = lastVolume;
            }
        });

        // === Speed ===
        speed.onchange = () => {
            video.playbackRate = parseFloat(speed.value);
        };

        // === Fullscreen ===
        fullscreenBtn.onclick = () => {
            if (video.requestFullscreen) video.requestFullscreen();
            else if (video.webkitRequestFullscreen) video.webkitRequestFullscreen();
        };

        applyGlobalSettings(video, muteBtn);
    }

    function applyGlobalSettings(video, muteBtn) {
        video.muted = isMuted;
        video.volume = lastVolume;
        muteBtn.textContent = isMuted ? "🔇" : "🔊";
    }

    function format(sec) {
        if (isNaN(sec)) return "0:00";
        let m = Math.floor(sec / 60);
        let s = Math.floor(sec % 60);
        return `${m}:${s.toString().padStart(2,"0")}`;
    }

    const observer = new MutationObserver(() => {
        document.querySelectorAll("video").forEach(initPlayer);
    });
    observer.observe(document.body, { childList: true, subtree: true });
})();