deafone

Instagram Video Controls (unmute, no autoplay, dynamic loading)

Jun 18th, 2025
64
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 5.71 KB | Source Code | 0 0
  1. // ==UserScript==
  2. // @name         Instagram Video Controls (unmute, no autoplay, dynamic loading)
  3. // @namespace    http://tampermonkey.net/
  4. // @version      2025-06-19
  5. // @description  Add controls to Instagram videos, unmute by default, disable autoplay, handle dynamic content, and provide Tampermonkey menu options for controls, sound, and autoplay blocking.
  6. // @author       You
  7. // @match        https://www.instagram.com/*
  8. // @grant        GM_registerMenuCommand
  9. // @grant        GM_getValue
  10. // @grant        GM_setValue
  11. // ==/UserScript==
  12.  
  13. (function () {
  14.     'use strict';
  15.  
  16.     // Default settings
  17.     const defaults = {
  18.         showControls: true,
  19.         unmute: true,
  20.         blockAutoplay: true
  21.     };
  22.  
  23.     // Load saved settings or use defaults
  24.     const settings = {
  25.         showControls: GM_getValue('showControls', defaults.showControls),
  26.         unmute: GM_getValue('unmute', defaults.unmute),
  27.         blockAutoplay: GM_getValue('blockAutoplay', defaults.blockAutoplay)
  28.     };
  29.  
  30.     // Toggle a boolean setting via Tampermonkey menu
  31.     const toggleSetting = (key, label) => {
  32.         const newValue = !settings[key];
  33.         settings[key] = newValue;
  34.         GM_setValue(key, newValue);
  35.         alert(`${label} set to ${newValue ? 'ON' : 'OFF'}. Refresh the page to apply changes.`);
  36.     };
  37.  
  38.     // Register menu commands
  39.     GM_registerMenuCommand(
  40.         `[${settings.showControls ? '✓' : ' '}] Show controls`,
  41.         () => toggleSetting('showControls', 'Show controls')
  42.     );
  43.     GM_registerMenuCommand(
  44.         `[${settings.unmute ? '✓' : ' '}] Unmute videos`,
  45.         () => toggleSetting('unmute', 'Unmute videos')
  46.     );
  47.     GM_registerMenuCommand(
  48.         `[${settings.blockAutoplay ? '✓' : ' '}] Block autoplay`,
  49.         () => toggleSetting('blockAutoplay', 'Block autoplay')
  50.     );
  51.  
  52.     const processed = new WeakSet();
  53.  
  54.     /**
  55.      * Enhance a single <video> element: add controls, unmute, disable autoplay attribute,
  56.      * bring to front, and optionally override play() to block Instagram-triggered autoplay.
  57.      * @param {HTMLVideoElement} video
  58.      */
  59.     const enhanceVideo = (video) => {
  60.         if (processed.has(video)) return;
  61.  
  62.         // 1. Show native controls if enabled
  63.         if (settings.showControls) {
  64.             video.controls = true;
  65.         }
  66.  
  67.         // 2. Unmute if enabled
  68.         if (settings.unmute) {
  69.             video.muted = false;
  70.             video.volume = 1.0;
  71.             video.removeAttribute('muted');
  72.             // Ensure Instagram scripts don't re-mute
  73.             video.addEventListener('loadedmetadata', () => { video.muted = false; });
  74.             video.addEventListener('play', () => { video.muted = false; });
  75.             video.addEventListener('volumechange', () => { if (video.muted) video.muted = false; });
  76.             video.addEventListener('timeupdate', () => { if (video.muted) video.muted = false; });
  77.         }
  78.  
  79.         // 3. Disable autoplay attribute/property
  80.         if (video.hasAttribute('autoplay')) {
  81.             video.removeAttribute('autoplay');
  82.         }
  83.         try {
  84.             video.autoplay = false;
  85.         } catch (e) {
  86.             // ignore if not writable
  87.         }
  88.  
  89.         // 4. Bring controls to front: adjust style
  90.         Object.assign(video.style, {
  91.             position: 'relative',
  92.             zIndex: '1000'
  93.         });
  94.  
  95.         // 5. Optionally override play() to block Instagram-triggered autoplay
  96.         if (settings.blockAutoplay && !video.__playOverridden) {
  97.             // Track user clicks on the video to allow manual play
  98.             video.addEventListener('click', () => {
  99.                 video._lastUserClick = Date.now();
  100.             });
  101.  
  102.             const originalPlay = video.play.bind(video);
  103.             video.play = (...args) => {
  104.                 // If user clicked recently (within 500ms), allow play
  105.                 if (video._lastUserClick && (Date.now() - video._lastUserClick) < 500) {
  106.                     return originalPlay(...args);
  107.                 }
  108.                 // Otherwise block autoplay attempts
  109.                 console.log('Blocked autoplay for video:', video);
  110.                 // Return a resolved promise to satisfy callers expecting a Promise
  111.                 return Promise.resolve();
  112.             };
  113.             video.__playOverridden = true;
  114.         }
  115.  
  116.         processed.add(video);
  117.     };
  118.  
  119.     // Process all existing <video> elements on the page
  120.     const enhanceAllExisting = () => {
  121.         document.querySelectorAll('video').forEach(enhanceVideo);
  122.     };
  123.  
  124.     // Observe DOM mutations to catch newly added videos
  125.     const observeNewVideos = () => {
  126.         const observer = new MutationObserver((mutations) => {
  127.             for (const mut of mutations) {
  128.                 for (const node of mut.addedNodes) {
  129.                     if (!(node instanceof HTMLElement)) continue;
  130.                     if (node.tagName === 'VIDEO') {
  131.                         enhanceVideo(node);
  132.                     } else {
  133.                         const vids = node.querySelectorAll?.('video');
  134.                         if (vids?.length) {
  135.                             vids.forEach(enhanceVideo);
  136.                         }
  137.                     }
  138.                 }
  139.             }
  140.         });
  141.         observer.observe(document.body, { childList: true, subtree: true });
  142.     };
  143.  
  144.     // Periodically re-run enhancement (for SPA navigation cases)
  145.     const periodicEnhance = () => {
  146.         setInterval(enhanceAllExisting, 2000);
  147.     };
  148.  
  149.     // Initial run
  150.     enhanceAllExisting();
  151.     observeNewVideos();
  152.     periodicEnhance();
  153.  
  154.     // Fallback: re-enhance on clicks (in case React re-renders)
  155.     document.addEventListener('click', enhanceAllExisting, true);
  156. })();
Add Comment
Please, Sign In to add comment