Instagram Video Controls

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

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

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.

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

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

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

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

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

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