Instagram — 2x while long left-click (hold) — robust

Hold the LEFT mouse button on a video/Reel to play at 2x while holding; release to restore speed/state. Robust: works when cursor is over overlays, pointer-capture kept, and enforces 2x while holding.

当前为 2025-08-26 提交的版本,查看 最新版本

// ==UserScript==
// @name         Instagram — 2x while long left-click (hold) — robust
// @namespace    https://gf.qytechs.cn/
// @version      1.2.0
// @description  Hold the LEFT mouse button on a video/Reel to play at 2x while holding; release to restore speed/state. Robust: works when cursor is over overlays, pointer-capture kept, and enforces 2x while holding.
// @match        *://www.instagram.com/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function () {
  'use strict';

  // ---------- CONFIG ----------
  const CONFIG = {
    speed: 2.0,             // playbackRate while holding
    holdMs: 450,            // ms to hold left button before triggering
    showOverlay: true,      // show small "2x" overlay while active
    overlayOffset: { x: 12, y: 12 }, // overlay offset from pointer
    moveCancelPx: 12,       // cancel if pointer moves this many pixels before trigger
    enforceIntervalMs: 180, // how often to re-set playbackRate while active
    overlayStyle: {
      position: 'absolute',
      padding: '6px 8px',
      background: 'rgba(0,0,0,0.72)',
      color: '#fff',
      borderRadius: '6px',
      fontSize: '13px',
      zIndex: 2147483647,
      pointerEvents: 'none',
      transition: 'opacity .12s linear'
    }
  };
  // ----------------------------

  let holdTimer = null;
  let active = false;            // whether 2x mode is active
  let targetVideo = null;        // the video we changed
  let savedRate = 1;             // previous playbackRate
  let savedPaused = true;        // whether video was paused before trigger
  let pointerId = null;
  let pointerDownTarget = null;  // element that received pointerdown (for pointer capture)
  let startX = 0, startY = 0;
  let suppressClick = false;     // whether to suppress the actual click (if long-press triggered)
  let overlayEl = null;
  let enforcerId = null;

  // create overlay element (lazy)
  function makeOverlay() {
    if (!CONFIG.showOverlay) return null;
    const d = document.createElement('div');
    d.textContent = `${CONFIG.speed}×`;
    Object.assign(d.style, CONFIG.overlayStyle);
    d.style.display = 'none';
    document.body.appendChild(d);
    return d;
  }

  function showOverlayAt(x, y) {
    if (!CONFIG.showOverlay) return;
    if (!overlayEl) overlayEl = makeOverlay();
    if (!overlayEl) return;
    overlayEl.style.left = (x + CONFIG.overlayOffset.x) + 'px';
    overlayEl.style.top = (y + CONFIG.overlayOffset.y) + 'px';
    overlayEl.style.display = 'block';
    overlayEl.style.opacity = '1';
  }

  function hideOverlay() {
    if (!overlayEl) return;
    overlayEl.style.opacity = '0';
    setTimeout(() => { if (overlayEl) overlayEl.style.display = 'none'; }, 140);
  }

  // Find a <video> element from point (robust when overlays cover video)
  function findVideoFromPoint(x, y) {
    if (!document.elementsFromPoint) {
      // fallback: elementFromPoint + search
      const el = document.elementFromPoint(x, y);
      return findVideoFromElement(el);
    }
    const elems = document.elementsFromPoint(x, y);
    for (const el of elems) {
      const v = findVideoFromElement(el);
      if (v) return v;
    }
    return null;
  }

  // Find video given an element (search children / ancestors)
  function findVideoFromElement(el) {
    try {
      if (!el) return null;
      if (el.tagName && el.tagName.toLowerCase() === 'video') return el;
      // if element contains a video
      if (el.querySelector) {
        const inside = el.querySelector('video');
        if (inside) return inside;
      }
      // if inside a video ancestor
      if (el.closest) {
        const anc = el.closest('video');
        if (anc) return anc;
      }
    } catch (e) {
      // ignore cross-origin or DOM exceptions
    }
    return null;
  }

  function startEnforcer() {
    // repeatedly set playbackRate while active to counteract page scripts
    if (enforcerId) clearInterval(enforcerId);
    enforcerId = setInterval(() => {
      try {
        if (active && targetVideo) targetVideo.playbackRate = CONFIG.speed;
      } catch (e) {}
    }, CONFIG.enforceIntervalMs);
  }

  function stopEnforcer() {
    if (enforcerId) {
      clearInterval(enforcerId);
      enforcerId = null;
    }
  }

  function triggerLongPress(video, clientX, clientY) {
    if (!video) return;
    targetVideo = video;
    savedRate = video.playbackRate || 1;
    savedPaused = !!video.paused;
    try {
      video.playbackRate = CONFIG.speed;
      // try to play so audio/visual effect is immediate (may be blocked by autoplay policy)
      video.play().catch(() => { /* ignore play rejection */ });
    } catch (e) {}
    active = true;
    suppressClick = true;
    showOverlayAt(clientX, clientY);
    startEnforcer();
  }

  function restoreState() {
    if (!active || !targetVideo) {
      active = false;
      targetVideo = null;
      savedRate = 1;
      savedPaused = true;
      hideOverlay();
      stopEnforcer();
      return;
    }
    try {
      targetVideo.playbackRate = savedRate;
      if (savedPaused) {
        try { targetVideo.pause(); } catch (_) {}
      }
    } catch (e) {}
    active = false;
    targetVideo = null;
    savedRate = 1;
    savedPaused = true;
    hideOverlay();
    stopEnforcer();
    // suppressClick remains true until a click event occurs and clears it
  }

  // pointerdown handler (capture)
  function onPointerDown(e) {
    // only care primary/left button
    if (e.button !== 0) return;
    // record pointerId & start position
    pointerId = e.pointerId ?? null;
    startX = e.clientX;
    startY = e.clientY;
    pointerDownTarget = e.target;

    // attempt pointer capture on the element that received the down event
    try { if (pointerDownTarget && pointerDownTarget.setPointerCapture) pointerDownTarget.setPointerCapture(pointerId); } catch (er) {}

    // find video element under the pointer (robust)
    const v = findVideoFromPoint(e.clientX, e.clientY);
    if (!v) return;

    // prevent default to reduce interference from site handlers (but allow selection if move)
    e.preventDefault && e.preventDefault();

    // start hold timer
    holdTimer = setTimeout(() => {
      holdTimer = null;
      triggerLongPress(v, e.clientX, e.clientY);
    }, CONFIG.holdMs);
  }

  function onPointerMove(e) {
    // If different pointer, ignore
    if (pointerId != null && e.pointerId !== pointerId) return;
    if (holdTimer) {
      const dx = e.clientX - startX;
      const dy = e.clientY - startY;
      const dist = Math.sqrt(dx * dx + dy * dy);
      if (dist > CONFIG.moveCancelPx) {
        clearTimeout(holdTimer);
        holdTimer = null;
      }
    }
    if (active && CONFIG.showOverlay) {
      showOverlayAt(e.clientX, e.clientY);
    }
  }

  function onPointerUp(e) {
    if (pointerId != null && e.pointerId !== pointerId) return;
    if (holdTimer) {
      clearTimeout(holdTimer);
      holdTimer = null;
    }
    if (active) restoreState();
    // release pointer capture if we set it
    try { if (pointerDownTarget && pointerDownTarget.releasePointerCapture) pointerDownTarget.releasePointerCapture(pointerId); } catch (_) {}
    pointerId = null;
    pointerDownTarget = null;
  }

  function onPointerCancel(e) {
    if (pointerId != null && e.pointerId !== pointerId) return;
    if (holdTimer) {
      clearTimeout(holdTimer);
      holdTimer = null;
    }
    if (active) restoreState();
    try { if (pointerDownTarget && pointerDownTarget.releasePointerCapture) pointerDownTarget.releasePointerCapture(pointerId); } catch (_) {}
    pointerId = null;
    pointerDownTarget = null;
  }

  // in case the element loses pointer capture unexpectedly
  function onLostPointerCapture(e) {
    // only respond if this was our pointer
    if (pointerId != null && e.pointerId !== pointerId) return;
    if (active) restoreState();
    if (holdTimer) { clearTimeout(holdTimer); holdTimer = null; }
    pointerId = null;
    pointerDownTarget = null;
  }

  // click handler to suppress the click that would otherwise toggle pause/play
  function onClickCapture(e) {
    if (suppressClick) {
      e.preventDefault && e.preventDefault();
      e.stopImmediatePropagation && e.stopImmediatePropagation();
      suppressClick = false;
      if (holdTimer) { clearTimeout(holdTimer); holdTimer = null; }
      return;
    }
  }

  // Add event listeners (capture)
  document.addEventListener('pointerdown', onPointerDown, { passive: false, capture: true });
  document.addEventListener('pointermove', onPointerMove, { passive: false, capture: true });
  document.addEventListener('pointerup', onPointerUp, { passive: false, capture: true });
  document.addEventListener('pointercancel', onPointerCancel, { passive: false, capture: true });
  document.addEventListener('click', onClickCapture, { passive: false, capture: true });
  // listen for lostpointercapture at document level (some elements will fire it)
  document.addEventListener('lostpointercapture', onLostPointerCapture, { capture: true });

  // safety listener for mouseup in case environment lacks pointer events
  document.addEventListener('mouseup', (e) => {
    if (e.button !== 0) return;
    if (holdTimer) { clearTimeout(holdTimer); holdTimer = null; }
    if (active) restoreState();
    if (pointerDownTarget && pointerDownTarget.releasePointerCapture) {
      try { pointerDownTarget.releasePointerCapture(pointerId); } catch (_) {}
    }
    pointerId = null;
    pointerDownTarget = null;
  }, { passive: false, capture: true });

  // Cleanup on navigation/unload
  window.addEventListener('beforeunload', () => {
    if (holdTimer) clearTimeout(holdTimer);
    if (active) restoreState();
    if (overlayEl && overlayEl.parentNode) overlayEl.parentNode.removeChild(overlayEl);
  });

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址