Instagram Video Controls

Provides enhanced video playback experience on Instagram with floating controls and multiple speed options

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         Instagram Video Controls
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Provides enhanced video playback experience on Instagram with floating controls and multiple speed options
// @author       se7
// @icon         https://www.google.com/s2/favicons?sz=64&domain=instagram.com
// @match        https://www.instagram.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  // SVG Icons
  const ICONS = {
    play: `<svg height="24" width="24" viewBox="0 0 24 24"><path fill="white" d="M8 5v14l11-7z"></path></svg>`,
    pause: `<svg height="24" width="24" viewBox="0 0 24 24"><path fill="white" d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"></path></svg>`,
  };

  function addVideoControls(video) {
    const videoWrapper = video.parentElement;
    if (!videoWrapper || videoWrapper.querySelector(".custom-video-controls")) {
      return;
    }

    videoWrapper.style.position = "relative";

    // --- Main Container for Controls ---
    const controlsContainer = document.createElement("div");
    controlsContainer.className = "custom-video-controls";
    const isStoryPage = window.location.pathname.startsWith('/stories/');

    Object.assign(controlsContainer.style, {
      position: "absolute",
      bottom: isStoryPage ? "75px" : "15px",
      left: "50%",
      transform: "translateX(-50%)",
      zIndex: "1000",
      display: "flex",
      gap: "12px",
      alignItems: "center",
      backgroundColor: "rgba(0, 0, 0, 0.7)",
      padding: "5px 15px",
      borderRadius: "50px",
      opacity: "0",
      transition: "opacity 0.3s ease-in-out",
      pointerEvents: "none",
    });

    // --- Show/Hide controls on hover ---
    videoWrapper.addEventListener("mouseenter", () => {
      controlsContainer.style.opacity = "1";
      controlsContainer.style.pointerEvents = "auto";
    });
    videoWrapper.addEventListener("mouseleave", () => {
      controlsContainer.style.opacity = "0";
      controlsContainer.style.pointerEvents = "none";
    });

    // --- Play/Pause Button with Icons ---
    const playPauseButton = document.createElement("button");
    playPauseButton.innerHTML = video.paused ? ICONS.play : ICONS.pause;
    Object.assign(playPauseButton.style, {
      background: "none",
      border: "none",
      cursor: "pointer",
      padding: "0",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
    });
    playPauseButton.addEventListener("click", (e) => {
      e.stopPropagation();
      video.paused ? video.play() : video.pause();
    });
    video.addEventListener(
      "play",
      () => (playPauseButton.innerHTML = ICONS.pause),
    );
    video.addEventListener(
      "pause",
      () => (playPauseButton.innerHTML = ICONS.play),
    );

    // --- Seek Bar ---
    const seekBar = document.createElement("input");
    seekBar.type = "range";
    seekBar.min = 0;
    seekBar.max = 100;
    seekBar.value = 0;
    Object.assign(seekBar.style, { width: "200px", cursor: "pointer" });
    video.addEventListener("timeupdate", () => {
      if (video.duration)
        seekBar.value = (video.currentTime / video.duration) * 100;
    });
    seekBar.addEventListener("input", () => {
      if (video.duration)
        video.currentTime = (seekBar.value / 100) * video.duration;
    });

    // --- Speed Selection Dropdown ---
    const speedSelector = document.createElement("select");
    Object.assign(speedSelector.style, {
      background: "none",
      border: "none",
      color: "white",
      cursor: "pointer",
      fontWeight: "bold",
      fontSize: "14px",
      appearance: "none", // Removes default browser styling
      textAlign: "center",
    });

    const speeds = [0.5, 0.75, 1, 1.25, 1.5, 2];
    speeds.forEach((speed) => {
      const option = document.createElement("option");
      option.value = speed;
      option.innerText = `${speed}x`;
      // Style the options inside the dropdown
      Object.assign(option.style, {
        backgroundColor: "rgba(0, 0, 0, 0.9)",
        color: "white",
      });
      speedSelector.appendChild(option);
    });

    speedSelector.value = "1"; // Default speed
    speedSelector.addEventListener("change", (e) => {
      e.stopPropagation();
      video.playbackRate = parseFloat(speedSelector.value);
    });

    // Prevents clicking on the dropdown from pausing the video
    speedSelector.addEventListener("click", (e) => e.stopPropagation());

    // --- Arrange and Add to Page ---
    controlsContainer.appendChild(playPauseButton);
    controlsContainer.appendChild(seekBar);
    controlsContainer.appendChild(speedSelector);
    videoWrapper.appendChild(controlsContainer);
  }

  // --- Observer for Dynamically Loaded Videos ---
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      mutation.addedNodes.forEach((node) => {
        if (node.nodeType === 1) {
          // ELEMENT_NODE
          if (node.tagName === "VIDEO") {
            addVideoControls(node);
          } else {
            node.querySelectorAll("video").forEach(addVideoControls);
          }
        }
      });
    });
  });

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

  document.querySelectorAll("video").forEach(addVideoControls);
})();