Instagram Video Controls

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

您需要先安装一款用户样式管理器扩展,比如 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);
})();