NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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 });
})();