asteriksme / Better Instagram

// ==UserScript==
// @name         Better Instagram
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Add video controls
// @author       asteriksme
// @match        https://www.instagram.com/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=instagram.com
// @updateURL    https://openuserjs.org/meta/asteriksme/Better_Instagram.meta.js
// @downloadURL  https://openuserjs.org/install/asteriksme/Better_Instagram.user.js
// @license      MIT
// @grant        none
// ==/UserScript==

/* jshint esversion: 11 */

(function () {
  const originalPlay = HTMLMediaElement.prototype.play;
  let gestureTimestamp = 0;
  const GESTURE_WINDOW_MS = 1000;

  const markGesture = () => { gestureTimestamp = Date.now(); };
  document.addEventListener('click', markGesture, true);
  document.addEventListener('keydown', markGesture, true);
  document.addEventListener('touchstart', markGesture, true);

  // Override the muted setter to block Instagram from re-muting
  const mutedDescriptor = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'muted');
  Object.defineProperty(HTMLMediaElement.prototype, 'muted', {
    get: mutedDescriptor.get,
    set: function (val) {
      if (val === true && this.dataset.forceUnmuted === '1') {
        console.debug('[userscript] blocked mute attempt');
        return;
      }
      mutedDescriptor.set.call(this, val);
    },
    configurable: true,
  });

  // Override the loop setter to always block looping
  const loopDescriptor = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'loop');
  Object.defineProperty(HTMLMediaElement.prototype, 'loop', {
    get: loopDescriptor.get,
    set: function (val) {
      if (val === true) {
        console.debug('[userscript] blocked loop attempt');
        return;
      }
      loopDescriptor.set.call(this, val);
    },
    configurable: true,
  });

  HTMLMediaElement.prototype.play = function () {
    const isUserGesture = (Date.now() - gestureTimestamp) < GESTURE_WINDOW_MS;
    if (!isUserGesture) {
      console.debug('[userscript] blocked autoplay on', this.src?.slice(0, 60));
      return Promise.resolve();
    }
    this.dataset.forceUnmuted = '1';
    mutedDescriptor.set.call(this, false);
    return originalPlay.apply(this, arguments);
  };

  function processVideo(video) {
    if (video.dataset.autoplayHandled) return;
    video.dataset.autoplayHandled = '1';
    video.pause();
    mutedDescriptor.set.call(video, false);
    loopDescriptor.set.call(video, false);
  }

  document.querySelectorAll('video').forEach(processVideo);

  const observer = new MutationObserver((mutations) => {
    for (const mutation of mutations) {
      for (const node of mutation.addedNodes) {
        if (node.nodeType !== 1) continue;
        if (node.tagName === 'VIDEO') processVideo(node);
        else if(node.querySelectorAll) node.querySelectorAll('video').forEach(processVideo);
      }
    }
  });

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