|
// ==UserScript== |
|
// @name Instagram Video Tools |
|
// @namespace https://github.com/jkluch |
|
// @version 1.0.0 |
|
// @description Easily control Instagram video behavior: auto-unmute, fullscreen on load or 'f', auto-play on maximize, start paused on load. Features are configurable via a simple settings panel (open "Instagram Video Settings" from Tampermonkey's menu). |
|
// @author jkluch |
|
// @license MIT |
|
// @match https://www.instagram.com/* |
|
// @updateURL https://gist.github.com/jkluch/c26d4cd76d07f4b47850cb4525ac5917/raw/instagram-video-tools.user.js |
|
// @downloadURL https://gist.github.com/jkluch/c26d4cd76d07f4b47850cb4525ac5917/raw/instagram-video-tools.user.js |
|
// @grant GM_registerMenuCommand |
|
// @grant GM_getValue |
|
// @grant GM_setValue |
|
// ==/UserScript== |
|
|
|
/* --- Embedded configuration UI --- */ |
|
var GM_config = { |
|
init: function(settings) { |
|
this.settings = settings; |
|
this.values = {}; |
|
for (var k in settings) { |
|
this.values[k] = typeof settings[k].default === "undefined" ? false : settings[k].default; |
|
} |
|
Object.keys(settings).forEach(async (k) => { |
|
const v = await GM_getValue(k); |
|
if (typeof v !== "undefined") this.values[k] = v; |
|
}); |
|
}, |
|
open: function() { |
|
const oldPanel = document.getElementById("gm_config_panel"); |
|
if (oldPanel) oldPanel.remove(); |
|
|
|
var wrapper = document.createElement("div"); |
|
wrapper.id = "gm_config_panel"; |
|
wrapper.style.position = "fixed"; |
|
wrapper.style.top = "10%"; |
|
wrapper.style.left = "50%"; |
|
wrapper.style.transform = "translateX(-50%)"; |
|
wrapper.style.background = "#222"; |
|
wrapper.style.color = "#fff"; |
|
wrapper.style.padding = "30px 40px"; |
|
wrapper.style.borderRadius = "10px"; |
|
wrapper.style.boxShadow = "0 0 40px rgba(0,0,0,0.7)"; |
|
wrapper.style.zIndex = "2147483648"; |
|
wrapper.style.fontFamily = "sans-serif"; |
|
wrapper.style.textAlign = "left"; |
|
wrapper.innerHTML = '<h2 style="margin-top:0;">Instagram Video Settings</h2>'; |
|
|
|
for (var k in this.settings) { |
|
var setting = this.settings[k]; |
|
var label = document.createElement("label"); |
|
label.style.display = "block"; |
|
label.style.margin = "16px 0"; |
|
label.style.fontSize = "1.08em"; |
|
|
|
var input = document.createElement("input"); |
|
input.type = "checkbox"; |
|
input.checked = this.values[k]; |
|
input.style.marginRight = "10px"; |
|
input.onchange = (function(key, gm_config) { return function() { |
|
gm_config.values[key] = this.checked; |
|
}; })(k, this); |
|
|
|
label.appendChild(input); |
|
label.appendChild(document.createTextNode(" " + setting.label)); |
|
wrapper.appendChild(label); |
|
} |
|
|
|
var saveBtn = document.createElement("button"); |
|
saveBtn.textContent = "Save"; |
|
saveBtn.style.marginRight = "20px"; |
|
saveBtn.style.padding = "8px 18px"; |
|
saveBtn.style.background = "#4caf50"; |
|
saveBtn.style.color = "#fff"; |
|
saveBtn.style.border = "none"; |
|
saveBtn.style.borderRadius = "5px"; |
|
saveBtn.style.fontWeight = "bold"; |
|
saveBtn.onclick = async () => { |
|
for (var k in this.values) { |
|
await GM_setValue(k, this.values[k]); |
|
} |
|
document.body.removeChild(wrapper); |
|
window.location.reload(); |
|
}; |
|
|
|
var cancelBtn = document.createElement("button"); |
|
cancelBtn.textContent = "Cancel"; |
|
cancelBtn.style.padding = "8px 18px"; |
|
cancelBtn.style.background = "#888"; |
|
cancelBtn.style.color = "#fff"; |
|
cancelBtn.style.border = "none"; |
|
cancelBtn.style.borderRadius = "5px"; |
|
cancelBtn.onclick = () => { |
|
document.body.removeChild(wrapper); |
|
}; |
|
|
|
wrapper.appendChild(saveBtn); |
|
wrapper.appendChild(cancelBtn); |
|
|
|
document.body.appendChild(wrapper); |
|
}, |
|
get: function(key) { |
|
return this.values[key]; |
|
} |
|
}; |
|
|
|
GM_config.init({ |
|
ENABLE_FULLSCREEN_ON_F: { |
|
label: "Enable fullscreen on 'f' key press", |
|
default: true |
|
}, |
|
ENABLE_AUTOPLAY_ON_FULLSCREEN: { |
|
label: "Auto-play when maximizing video", |
|
default: true |
|
}, |
|
ENABLE_UNMUTE_ON_LOAD: { |
|
label: "Unmute videos on load and when new videos appear", |
|
default: true |
|
}, |
|
ENABLE_FULLSCREEN_ON_LOAD: { |
|
label: "Fullscreen video on initial page load", |
|
default: true |
|
}, |
|
ENABLE_START_PAUSED_ON_LOAD: { |
|
label: "Start video paused on initial page load", |
|
default: false |
|
} |
|
}); |
|
|
|
GM_registerMenuCommand("Instagram Video Settings", function() { |
|
GM_config.open(); |
|
}); |
|
|
|
(async function() { |
|
'use strict'; |
|
|
|
for (const key in GM_config.settings) { |
|
const v = await GM_getValue(key); |
|
if (typeof v !== "undefined") GM_config.values[key] = v; |
|
} |
|
|
|
const ENABLE_FULLSCREEN_ON_F = GM_config.get("ENABLE_FULLSCREEN_ON_F"); |
|
const ENABLE_AUTOPLAY_ON_FULLSCREEN = GM_config.get("ENABLE_AUTOPLAY_ON_FULLSCREEN"); |
|
const ENABLE_UNMUTE_ON_LOAD = GM_config.get("ENABLE_UNMUTE_ON_LOAD"); |
|
const ENABLE_FULLSCREEN_ON_LOAD = GM_config.get("ENABLE_FULLSCREEN_ON_LOAD"); |
|
const ENABLE_START_PAUSED_ON_LOAD = GM_config.get("ENABLE_START_PAUSED_ON_LOAD"); |
|
|
|
function unmuteAllVideos() { |
|
if (!ENABLE_UNMUTE_ON_LOAD) return; |
|
const videos = document.querySelectorAll('video'); |
|
videos.forEach(video => { |
|
video.muted = false; |
|
video.volume = 1.0; |
|
}); |
|
} |
|
|
|
function pauseAllVideosOnLoad() { |
|
const videos = document.querySelectorAll('video'); |
|
videos.forEach(video => { |
|
video.pause(); |
|
}); |
|
} |
|
|
|
// Only pause videos once, on initial load |
|
if (ENABLE_START_PAUSED_ON_LOAD) { |
|
window.addEventListener('load', pauseAllVideosOnLoad); |
|
} |
|
|
|
// Only unmute logic for MutationObserver and scroll events |
|
if (ENABLE_UNMUTE_ON_LOAD) { |
|
window.addEventListener('load', unmuteAllVideos); |
|
const observer = new MutationObserver(() => unmuteAllVideos()); |
|
observer.observe(document.body, { childList: true, subtree: true }); |
|
document.addEventListener('scroll', () => setTimeout(unmuteAllVideos, 500)); |
|
} |
|
|
|
let popupDiv = null; |
|
let movedVideo = null; |
|
let originalParent = null; |
|
let originalNextSibling = null; |
|
let originalStyles = null; |
|
let originalControls = null; |
|
let resizeVideo = null; |
|
let handleEscape = null; |
|
let debugListeners = []; |
|
|
|
function addDebugListeners(video) { |
|
debugListeners.forEach(({event, handler}) => { |
|
video.removeEventListener(event, handler); |
|
}); |
|
debugListeners = []; |
|
|
|
const log = (msg) => { |
|
console.log(`[IG Video Debug] ${msg} | muted=${video.muted} volume=${video.volume} paused=${video.paused} currentTime=${video.currentTime}`); |
|
}; |
|
|
|
const playHandler = () => log('play event'); |
|
const pauseHandler = () => log('pause event'); |
|
const volumeHandler = () => { |
|
if (video.muted) { |
|
log('muted event'); |
|
} else { |
|
log('unmuted event'); |
|
} |
|
}; |
|
const endedHandler = () => log('ended event'); |
|
|
|
video.addEventListener('play', playHandler); |
|
video.addEventListener('pause', pauseHandler); |
|
video.addEventListener('volumechange', volumeHandler); |
|
video.addEventListener('ended', endedHandler); |
|
|
|
debugListeners = [ |
|
{event: 'play', handler: playHandler}, |
|
{event: 'pause', handler: pauseHandler}, |
|
{event: 'volumechange', handler: volumeHandler}, |
|
{event: 'ended', handler: endedHandler} |
|
]; |
|
} |
|
|
|
function popupLargestVideo({autoplayIfPaused = false} = {}) { |
|
if (popupDiv) return; |
|
|
|
const videos = document.querySelectorAll('video'); |
|
if (videos.length === 0) return; |
|
|
|
let targetVideo = videos[0]; |
|
let maxArea = 0; |
|
videos.forEach(video => { |
|
const rect = video.getBoundingClientRect(); |
|
const area = rect.width * rect.height; |
|
if (area > maxArea && rect.width > 100 && rect.height > 100 && rect.top >= 0 && rect.left >= 0) { |
|
targetVideo = video; |
|
maxArea = area; |
|
} |
|
}); |
|
|
|
originalParent = targetVideo.parentNode; |
|
originalNextSibling = targetVideo.nextSibling; |
|
originalStyles = targetVideo.getAttribute('style'); |
|
originalControls = targetVideo.controls; |
|
|
|
popupDiv = document.createElement('div'); |
|
popupDiv.style.position = 'fixed'; |
|
popupDiv.style.zIndex = '2147483647'; |
|
popupDiv.style.top = '0'; |
|
popupDiv.style.left = '0'; |
|
popupDiv.style.width = '100vw'; |
|
popupDiv.style.height = '100vh'; |
|
popupDiv.style.display = 'flex'; |
|
popupDiv.style.alignItems = 'center'; |
|
popupDiv.style.justifyContent = 'center'; |
|
popupDiv.style.background = 'rgba(0,0,0,0.85)'; |
|
popupDiv.style.boxSizing = 'border-box'; |
|
popupDiv.style.overflow = 'hidden'; |
|
|
|
targetVideo.style.position = 'absolute'; |
|
targetVideo.style.zIndex = '2'; |
|
targetVideo.style.top = '0'; |
|
targetVideo.style.left = '0'; |
|
targetVideo.style.width = '100vw'; |
|
targetVideo.style.height = '100vh'; |
|
targetVideo.style.boxShadow = '0 0 40px rgba(0,0,0,0.7)'; |
|
targetVideo.style.background = '#000'; |
|
targetVideo.style.border = 'none'; |
|
targetVideo.style.borderRadius = '0'; |
|
targetVideo.style.objectFit = 'contain'; |
|
targetVideo.controls = true; |
|
|
|
popupDiv.appendChild(targetVideo); |
|
document.body.appendChild(popupDiv); |
|
movedVideo = targetVideo; |
|
|
|
movedVideo.setAttribute('tabindex', '0'); |
|
movedVideo.focus(); |
|
|
|
addDebugListeners(movedVideo); |
|
|
|
resizeVideo = function() { |
|
if (movedVideo) { |
|
movedVideo.style.width = window.innerWidth + 'px'; |
|
movedVideo.style.height = window.innerHeight + 'px'; |
|
} |
|
}; |
|
resizeVideo(); |
|
window.addEventListener('resize', resizeVideo); |
|
|
|
if (autoplayIfPaused && movedVideo.paused) { |
|
movedVideo.muted = true; |
|
movedVideo.volume = 1.0; |
|
let playPromise = movedVideo.play(); |
|
if (playPromise !== undefined) { |
|
playPromise |
|
.then(() => { |
|
setTimeout(() => { |
|
movedVideo.muted = false; |
|
movedVideo.volume = 1.0; |
|
}, 200); |
|
}) |
|
.catch((err) => { |
|
// Autoplay failed, do nothing |
|
}); |
|
} |
|
} |
|
|
|
handleEscape = function(ev) { |
|
if ((ev.key === 'Escape' || ev.key === 'f') && popupDiv) { |
|
restoreVideo(); |
|
} |
|
}; |
|
document.addEventListener('keydown', handleEscape); |
|
|
|
popupDiv.addEventListener('click', function(ev) { |
|
if (ev.target === popupDiv) { |
|
restoreVideo(); |
|
} |
|
}); |
|
|
|
popupDiv.addEventListener('mousedown', function(ev) { |
|
if (ev.target === movedVideo) { |
|
setTimeout(() => movedVideo.focus(), 0); |
|
} |
|
}); |
|
movedVideo.addEventListener('blur', () => { |
|
setTimeout(() => { |
|
if (popupDiv) movedVideo.focus(); |
|
}, 0); |
|
}); |
|
|
|
function restoreVideo() { |
|
if (movedVideo) { |
|
if (originalStyles !== null) { |
|
movedVideo.setAttribute('style', originalStyles); |
|
} else { |
|
movedVideo.removeAttribute('style'); |
|
} |
|
movedVideo.controls = originalControls; |
|
|
|
window.removeEventListener('resize', resizeVideo); |
|
|
|
debugListeners.forEach(({event, handler}) => { |
|
movedVideo.removeEventListener(event, handler); |
|
}); |
|
debugListeners = []; |
|
|
|
if (originalNextSibling) { |
|
originalParent.insertBefore(movedVideo, originalNextSibling); |
|
} else { |
|
originalParent.appendChild(movedVideo); |
|
} |
|
movedVideo = null; |
|
} |
|
if (popupDiv) { |
|
popupDiv.remove(); |
|
popupDiv = null; |
|
document.removeEventListener('keydown', handleEscape); |
|
} |
|
resizeVideo = null; |
|
handleEscape = null; |
|
} |
|
} |
|
|
|
if (ENABLE_FULLSCREEN_ON_LOAD) { |
|
window.addEventListener('load', function() { |
|
setTimeout(() => popupLargestVideo({ |
|
autoplayIfPaused: false |
|
}), 10); |
|
}); |
|
} |
|
|
|
if (ENABLE_FULLSCREEN_ON_F) { |
|
document.addEventListener('keydown', function(e) { |
|
if (e.key === 'f' && !e.ctrlKey && !e.metaKey && !e.altKey) { |
|
if (popupDiv) { |
|
const escEvent = new KeyboardEvent('keydown', {key: 'Escape'}); |
|
document.dispatchEvent(escEvent); |
|
} else { |
|
popupLargestVideo({ |
|
autoplayIfPaused: ENABLE_AUTOPLAY_ON_FULLSCREEN |
|
}); |
|
} |
|
} |
|
}); |
|
} |
|
})(); |