Greasy Fork is available in English.
Universal video rotate, zoom, Hand Tool panning, resolution selection, fullscreen tools, faster exact Facebook comment-video downloads, and reliable first-open Instagram Story resolution
// ==UserScript==
// @name Universal Video Tools
// @namespace @reinaldyalaratte
// @version 10.4
// @description Universal video rotate, zoom, Hand Tool panning, resolution selection, fullscreen tools, faster exact Facebook comment-video downloads, and reliable first-open Instagram Story resolution
// @match *://*/*
// @icon https://64.media.tumblr.com/6fcb9e154ad902b3790ade5fbc06c6fb/c8ffa58e4f9eec8b-dd/s1280x1920/e0486cc9c4ec361b49df663a48175d87effa59e9.pnj
// @grant GM_download
// @grant GM_xmlhttpRequest
// @grant GM_openInTab
// @grant unsafeWindow
// @connect *
// @connect i.instagram.com
// @run-at document-start
// @license CC-BY-NC
// ==/UserScript==
(function () {
'use strict';
// ============================================================
// CONFIGURATION
// ============================================================
const CONFIG = {
zoomStep: 0.25,
minZoom: 0.50,
maxZoom: 8.00,
scanInterval: 1000,
scanDebounce: 70,
minVisiblePixels: 8,
minLikelyVideoArea: 3200,
minPlayingVideoArea: 240,
dragMargin: 6,
fullscreenIdleMs: 3000,
playbackRestoreDelays: [0, 50, 180, 450],
mediaUrlMaxAgeMs: 6 * 60 * 60 * 1000,
networkTimeoutMs: 45000
};
const PAGE = (
typeof unsafeWindow !== 'undefined'
? unsafeWindow
: window
);
// Prevent V10.0 from running twice in the same document/frame.
try {
if (PAGE.__VRZ_V104_ACTIVE__) {
return;
}
PAGE.__VRZ_V104_ACTIVE__ = true;
} catch {
if (window.__VRZ_V104_ACTIVE__) {
return;
}
window.__VRZ_V104_ACTIVE__ = true;
}
// ============================================================
// INITIAL GLOBAL STATE
// ============================================================
let ROOT = null;
let uiStarted = false;
let externalScheduleScan = null;
const capturedShadowRoots = new Set();
const mediaUrlStore = new Map();
const instagramMediaSourceStore = new Map();
const instagramUrlIdentityStore = new Map();
const instagramStoryContextStore = new Map();
const facebookVideoSourceStore = new Map();
const facebookUrlIdentityStore = new Map();
const capturedHlsInstances = new Set();
// ============================================================
// BASIC HELPERS
// ============================================================
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
function normalizeAngle(value) {
return ((value % 360) + 360) % 360;
}
function nowMs() {
return Date.now();
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function safeUrl(value, base = location.href) {
if (!value) {
return '';
}
try {
return new URL(String(value), base).href;
} catch {
return String(value).trim();
}
}
function sanitizeFilename(value) {
return (value || 'video')
.replace(/[\\/:*?"<>|]+/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.slice(0, 120) || 'video';
}
function setImportant(element, property, value) {
if (!element) {
return;
}
element.style.setProperty(property, value, 'important');
}
function saveStyles(element, properties) {
const result = {};
for (const property of properties) {
result[property] = {
value: element.style.getPropertyValue(property),
priority: element.style.getPropertyPriority(property)
};
}
return result;
}
function restoreStyles(element, styles) {
if (!element || !styles) {
return;
}
for (const [property, data] of Object.entries(styles)) {
if (data.value) {
element.style.setProperty(
property,
data.value,
data.priority
);
} else {
element.style.removeProperty(property);
}
}
}
function isElementConnected(element) {
return Boolean(element && element.isConnected);
}
// ============================================================
// CAPTURE HLS.JS INSTANCES EARLY
//
// This makes resolution switching work on many sites that use
// hls.js with MediaSource instead of native HLS playback.
// ============================================================
function wrapHlsConstructor(HlsClass) {
if (
typeof HlsClass !== 'function' ||
HlsClass.__vrzV9Wrapped
) {
return HlsClass;
}
try {
let WrappedHls = null;
WrappedHls = new Proxy(HlsClass, {
construct(target, args, newTarget) {
const effectiveNewTarget = (
newTarget === WrappedHls
? target
: newTarget
);
const instance = Reflect.construct(
target,
args,
effectiveNewTarget
);
try {
capturedHlsInstances.add(instance);
} catch {
// Ignore.
}
return instance;
},
apply(target, thisArg, args) {
return Reflect.apply(target, thisArg, args);
}
});
Object.defineProperty(
WrappedHls,
'__vrzV9Wrapped',
{
value: true,
configurable: false
}
);
return WrappedHls;
} catch {
return HlsClass;
}
}
function installHlsInstanceCapture() {
try {
const descriptor = Object.getOwnPropertyDescriptor(PAGE, 'Hls');
if (descriptor && descriptor.configurable === false) {
return;
}
if (typeof PAGE.Hls === 'function') {
PAGE.Hls = wrapHlsConstructor(PAGE.Hls);
return;
}
let storedHls = PAGE.Hls;
Object.defineProperty(PAGE, 'Hls', {
configurable: true,
enumerable: true,
get() {
return storedHls;
},
set(value) {
storedHls = wrapHlsConstructor(value);
}
});
} catch {
// Some sites lock global properties.
}
}
// ============================================================
// CAPTURE MEDIA URLS FROM NETWORK ACTIVITY
//
// Installed at document-start so video/manifest URLs that
// appear before the player is fully created can still be detected.
// ============================================================
function isImageLikeUrl(url) {
const source = String(url || '').toLowerCase();
return (
/\.(?:jpe?g|jfif|png|webp|gif|avif|bmp|svg)(?:$|[?#])/i.test(source) ||
/(?:^|[?&])(?:format|ext|type)=(?:jpe?g|jfif|png|webp|gif|avif)(?:&|$)/i.test(source)
);
}
function isAudioLikeUrl(url) {
const source = String(url || '').toLowerCase();
let decoded = source;
try {
decoded = decodeURIComponent(source);
} catch {
// Keep the original string.
}
return (
/\.(?:m4a|mp3|aac|opus|wav|flac)(?:$|[?#])/i.test(source) ||
/(?:^|[?&])mime=audio(?:%2f|\/)/i.test(source) ||
decoded.includes('mime=audio/')
);
}
function isVideoLikeUrl(url) {
const source = String(url || '').toLowerCase();
let decoded = source;
try {
decoded = decodeURIComponent(source);
} catch {
// Keep the original string.
}
return (
/\.(?:mp4|webm|m4v|mov|mkv|ogv|ogg|flv|ts)(?:$|[?#])/i.test(source) ||
/(?:^|[?&])mime=video(?:%2f|\/)/i.test(source) ||
decoded.includes('mime=video/')
);
}
function isPartialMediaUrl(url) {
const raw = String(url || '');
let decoded = raw;
// Instagram and Meta sometimes expose a single byte-range fragment as
// a URL that still looks like an MP4. Saving that URL creates a file
// with an MP4 header but no complete playable movie (black screen).
for (let index = 0; index < 2; index += 1) {
try {
const next = decodeURIComponent(decoded);
if (next === decoded) {
break;
}
decoded = next;
} catch {
break;
}
}
const source = decoded.toLowerCase();
return Boolean(
/[?&](?:bytestart|byteend|start_byte|end_byte|byte_range|range)=/i.test(source) ||
/[?&]bytes=\d+(?:-|%2d)\d*/i.test(source) ||
/(?:^|[/_.-])(?:segment|fragment|frag|chunk|part)[-_/]?\d+(?:[/?#._-]|$)/i.test(source) ||
/\.(?:m4s|cmfv|cmfa)(?:$|[?#])/i.test(source)
);
}
function classifyMediaUrl(url, contentType = '', initiatorType = '') {
const source = String(url || '').toLowerCase();
const type = String(contentType || '').toLowerCase();
const initiator = String(initiatorType || '').toLowerCase();
if (!source || source.startsWith('data:')) {
return 'unknown';
}
// Never let posters/thumbnails become download candidates.
if (
isImageLikeUrl(source) ||
type.startsWith('image/')
) {
return 'unknown';
}
if (
isAudioLikeUrl(source) ||
type.startsWith('audio/')
) {
return 'audio';
}
if (source.startsWith('blob:')) {
return 'blob';
}
if (
/\.m3u8(?:$|[?#])/i.test(source) ||
type.includes('mpegurl') ||
type.includes('vnd.apple.mpegurl')
) {
return 'hls';
}
if (
/\.mpd(?:$|[?#])/i.test(source) ||
type.includes('dash+xml')
) {
return 'dash';
}
if (isPartialMediaUrl(source)) {
return 'segment';
}
if (
/\.(m4s|cmfv|cmfa|ts|aac)(?:$|[?#])/i.test(source) ||
/(?:^|[/_.-])(segment|fragment|frag|chunk|part)[-_./?=&]/i.test(source) ||
/[?&](?:range|sq|segment|fragment|chunk|part)=/i.test(source)
) {
return 'segment';
}
if (
isVideoLikeUrl(source) ||
type.startsWith('video/') ||
(
initiator === 'video' &&
!isImageLikeUrl(source) &&
!isAudioLikeUrl(source)
)
) {
return 'direct';
}
if (
/(?:video[_-]?url|media[_-]?url|stream[_-]?url|playback[_-]?url)/i.test(source)
) {
return 'direct';
}
// YouTube uses /videoplayback for both audio-only and video streams.
// It is only treated as video when the URL does not explicitly say audio.
if (
/(?:^|\/)videoplayback(?:[/?#]|$)/i.test(source) &&
!isAudioLikeUrl(source)
) {
return 'direct';
}
return 'unknown';
}
function rememberMediaUrl(url, details = {}) {
const normalized = safeUrl(url);
if (!normalized || normalized.startsWith('data:')) {
return;
}
const kind = details.kind || classifyMediaUrl(
normalized,
details.contentType,
details.initiatorType
);
if (kind === 'unknown') {
return;
}
const previous = mediaUrlStore.get(normalized) || {};
mediaUrlStore.set(normalized, {
url: normalized,
kind,
contentType: details.contentType || previous.contentType || '',
source: details.source || previous.source || 'network',
initiatorType: details.initiatorType || previous.initiatorType || '',
timestamp: nowMs()
});
// Remove old URLs so the store does not grow forever.
if (mediaUrlStore.size > 800) {
const cutoff = nowMs() - CONFIG.mediaUrlMaxAgeMs;
for (const [key, value] of mediaUrlStore) {
if (value.timestamp < cutoff || mediaUrlStore.size > 650) {
mediaUrlStore.delete(key);
}
}
}
}
// ============================================================
// INSTAGRAM MEDIA RESPONSE CAPTURE (V9.7)
//
// Instagram home, Explore, profile grids, likes, and other feeds are SPAs.
// The browser URL may stay at / while the visible post changes. Capture
// fresh JSON responses and index each media item by shortcode/media ID so
// the downloader can resolve the video currently visible on screen.
// ============================================================
function isInstagramHostname(hostname = location.hostname) {
const host = String(hostname || '').toLowerCase();
return (
host === 'instagram.com' ||
host.endsWith('.instagram.com') ||
host === 'i.instagram.com'
);
}
function normalizeInstagramShortcode(value) {
const textValue = String(value || '').trim();
const match = textValue.match(/^[A-Za-z0-9_-]{5,40}$/);
return match ? match[0] : '';
}
function normalizeInstagramMediaId(value) {
const textValue = String(value || '').trim();
const match = textValue.match(/^(\d{5,40})(?:_\d+)?$/);
return match ? match[1] : '';
}
function normalizeInstagramUsername(value) {
const textValue = String(value || '').trim().replace(/^@/, '');
const match = textValue.match(/^[A-Za-z0-9._]{1,40}$/);
return match ? match[0].toLowerCase() : '';
}
function instagramIdentityKeys(identity = {}) {
const keys = [];
const shortcode = normalizeInstagramShortcode(identity.shortcode);
const mediaId = normalizeInstagramMediaId(identity.mediaId || identity.id);
const storyId = normalizeInstagramMediaId(identity.storyId);
if (shortcode) keys.push(`code:${shortcode}`);
if (mediaId) keys.push(`id:${mediaId}`);
if (storyId && storyId !== mediaId) keys.push(`story:${storyId}`);
return keys;
}
function rememberInstagramStoryContext(context = {}) {
const username = normalizeInstagramUsername(context.username);
const userId = normalizeInstagramMediaId(context.userId || context.ownerId);
const storyId = normalizeInstagramMediaId(context.storyId || context.mediaId);
const highlightId = normalizeInstagramMediaId(context.highlightId);
const timestamp = nowMs();
const value = {
username,
userId,
storyId,
highlightId,
timestamp
};
if (username) instagramStoryContextStore.set(`user:${username}`, value);
if (userId) instagramStoryContextStore.set(`user-id:${userId}`, value);
if (storyId) instagramStoryContextStore.set(`story:${storyId}`, value);
if (highlightId) instagramStoryContextStore.set(`highlight:${highlightId}`, value);
if (instagramStoryContextStore.size > 500) {
const cutoff = nowMs() - CONFIG.mediaUrlMaxAgeMs;
for (const [key, item] of instagramStoryContextStore) {
if (
Number(item?.timestamp || 0) < cutoff ||
instagramStoryContextStore.size > 380
) {
instagramStoryContextStore.delete(key);
}
}
}
}
function getInstagramStoryContext(identity = {}) {
const username = normalizeInstagramUsername(identity.username);
const userId = normalizeInstagramMediaId(identity.userId);
const storyId = normalizeInstagramMediaId(identity.storyId || identity.mediaId);
const highlightId = normalizeInstagramMediaId(identity.highlightId);
return (
(storyId && instagramStoryContextStore.get(`story:${storyId}`)) ||
(highlightId && instagramStoryContextStore.get(`highlight:${highlightId}`)) ||
(userId && instagramStoryContextStore.get(`user-id:${userId}`)) ||
(username && instagramStoryContextStore.get(`user:${username}`)) ||
null
);
}
function getInstagramStoryWarmupKey(identity = {}) {
const storyId = normalizeInstagramMediaId(
identity.storyId || identity.mediaId
);
const highlightId = normalizeInstagramMediaId(identity.highlightId);
const userId = normalizeInstagramMediaId(
identity.userId || getInstagramStoryContext(identity)?.userId
);
const username = normalizeInstagramUsername(identity.username);
if (storyId) return `story:${storyId}`;
if (highlightId) return `highlight:${highlightId}`;
if (userId) return `story-user-id:${userId}`;
if (username) return `story-user:${username}`;
return '';
}
function getInstagramUrlIndexKey(value) {
const normalized = safeUrl(value);
if (!normalized || !/^https?:/i.test(normalized)) {
return '';
}
try {
const url = new URL(normalized);
return `${url.hostname.toLowerCase()}${url.pathname}`;
} catch {
return normalized.split('?')[0];
}
}
function rememberInstagramMediaSource(identity, url, details = {}) {
const normalized = safeUrl(url);
const keys = instagramIdentityKeys(identity);
if (
!normalized ||
!keys.length ||
normalized.startsWith('data:') ||
isImageLikeUrl(normalized) ||
isAudioLikeUrl(normalized) ||
isPartialMediaUrl(normalized)
) {
return;
}
const kind = details.kind || classifyMediaUrl(
normalized,
details.contentType || 'video/mp4',
details.initiatorType || ''
);
if (!['direct', 'hls', 'dash'].includes(kind)) {
return;
}
const record = {
url: normalized,
kind,
source: details.source || 'instagram-network-json',
contentType: details.contentType || (kind === 'direct' ? 'video/mp4' : ''),
likelyMuxed: details.likelyMuxed !== false && kind === 'direct',
width: Number(details.width || 0),
height: Number(details.height || 0),
bitrate: Number(details.bitrate || 0),
scoreBonus: Number(details.scoreBonus || 0),
timestamp: nowMs(),
identity: {
shortcode: normalizeInstagramShortcode(identity.shortcode),
mediaId: normalizeInstagramMediaId(identity.mediaId || identity.id),
storyId: normalizeInstagramMediaId(identity.storyId),
username: normalizeInstagramUsername(identity.username),
userId: normalizeInstagramMediaId(identity.userId),
highlightId: normalizeInstagramMediaId(identity.highlightId),
surface: identity.surface || '',
url: identity.url || ''
}
};
if (record.identity.surface === 'story' || record.identity.storyId) {
rememberInstagramStoryContext({
username: record.identity.username,
userId: record.identity.userId,
storyId: record.identity.storyId || record.identity.mediaId,
highlightId: record.identity.highlightId
});
}
for (const key of keys) {
let records = instagramMediaSourceStore.get(key);
if (!records) {
records = new Map();
instagramMediaSourceStore.set(key, records);
}
const previous = records.get(normalized);
records.set(normalized, {
...previous,
...record,
scoreBonus: Math.max(
Number(previous?.scoreBonus || 0),
Number(record.scoreBonus || 0)
)
});
}
const urlKey = getInstagramUrlIndexKey(normalized);
if (urlKey) {
instagramUrlIdentityStore.set(urlKey, {
...record.identity,
timestamp: record.timestamp
});
}
if (instagramMediaSourceStore.size > 300) {
const cutoff = nowMs() - CONFIG.mediaUrlMaxAgeMs;
for (const [key, records] of instagramMediaSourceStore) {
let newest = 0;
for (const item of records.values()) {
newest = Math.max(newest, Number(item.timestamp || 0));
}
if (newest < cutoff || instagramMediaSourceStore.size > 240) {
instagramMediaSourceStore.delete(key);
}
}
}
if (instagramUrlIdentityStore.size > 1200) {
const cutoff = nowMs() - CONFIG.mediaUrlMaxAgeMs;
for (const [key, value] of instagramUrlIdentityStore) {
if (
Number(value.timestamp || 0) < cutoff ||
instagramUrlIdentityStore.size > 950
) {
instagramUrlIdentityStore.delete(key);
}
}
}
}
function captureInstagramStoryContextsFromObject(
value,
depth = 0,
seen = new WeakSet()
) {
if (
value === null ||
value === undefined ||
depth > 20
) {
return;
}
if (Array.isArray(value)) {
for (const item of value.slice(0, 3000)) {
captureInstagramStoryContextsFromObject(
item,
depth + 1,
seen
);
}
return;
}
if (typeof value !== 'object' || seen.has(value)) {
return;
}
seen.add(value);
const user = (
value.user && typeof value.user === 'object'
? value.user
: null
);
const items = Array.isArray(value.items) ? value.items : [];
const username = normalizeInstagramUsername(
user?.username ||
value.username ||
value.owner?.username ||
''
);
const userId = normalizeInstagramMediaId(
user?.pk ||
user?.id ||
value.user_id ||
value.owner?.pk ||
value.owner?.id ||
''
);
if (items.length && (username || userId)) {
for (const item of items.slice(0, 500)) {
const storyId = normalizeInstagramMediaId(
item?.pk || item?.id || item?.media_id || ''
);
if (!storyId) {
continue;
}
rememberInstagramStoryContext({
username,
userId,
storyId
});
}
}
if (value.reels && typeof value.reels === 'object') {
for (const [reelKey, reel] of Object.entries(value.reels)) {
if (!reel || typeof reel !== 'object') {
continue;
}
const reelUser = reel.user || {};
const reelUsername = normalizeInstagramUsername(
reelUser.username || username
);
const reelUserId = normalizeInstagramMediaId(
reelUser.pk ||
reelUser.id ||
(/^\d+$/.test(reelKey) ? reelKey : '') ||
userId
);
const highlightId = normalizeInstagramMediaId(
reelKey.startsWith('highlight:')
? reelKey.slice('highlight:'.length)
: ''
);
if (highlightId) {
rememberInstagramStoryContext({
username: reelUsername,
userId: reelUserId,
highlightId
});
}
for (const item of Array.isArray(reel.items) ? reel.items : []) {
const storyId = normalizeInstagramMediaId(
item?.pk || item?.id || item?.media_id || ''
);
if (storyId) {
rememberInstagramStoryContext({
username: reelUsername,
userId: reelUserId,
storyId,
highlightId
});
}
}
}
}
for (const child of Object.values(value)) {
if (child && typeof child === 'object') {
captureInstagramStoryContextsFromObject(
child,
depth + 1,
seen
);
}
}
}
function extractInstagramMediaSourcesFromObject(
value,
inheritedIdentity = {},
source = 'instagram-network-json',
depth = 0,
seen = new WeakSet()
) {
if (
value === null ||
value === undefined ||
depth > 22
) {
return;
}
if (Array.isArray(value)) {
for (const item of value.slice(0, 3000)) {
extractInstagramMediaSourcesFromObject(
item,
inheritedIdentity,
source,
depth + 1,
seen
);
}
return;
}
if (typeof value !== 'object') {
return;
}
if (seen.has(value)) {
return;
}
seen.add(value);
const hasMediaFields = Boolean(
Array.isArray(value.video_versions) ||
value.video_url ||
value.playback_url ||
value.media_type === 2 ||
value.__typename === 'GraphVideo' ||
value.is_video === true
);
const localIdentity = {
shortcode: normalizeInstagramShortcode(
value.code ||
value.shortcode ||
value.media_code ||
inheritedIdentity.shortcode
),
mediaId: normalizeInstagramMediaId(
value.pk ||
(hasMediaFields ? value.id : '') ||
value.media_id ||
inheritedIdentity.mediaId
),
storyId: normalizeInstagramMediaId(
value.story_id ||
inheritedIdentity.storyId ||
''
),
username: normalizeInstagramUsername(
value.user?.username ||
value.owner?.username ||
inheritedIdentity.username ||
''
),
userId: normalizeInstagramMediaId(
value.user?.pk ||
value.user?.id ||
value.owner?.pk ||
value.owner?.id ||
inheritedIdentity.userId ||
''
),
highlightId: normalizeInstagramMediaId(
inheritedIdentity.highlightId || ''
),
surface: inheritedIdentity.surface || '',
url: inheritedIdentity.url || ''
};
if (
hasMediaFields &&
!localIdentity.storyId &&
source.includes('story')
) {
localIdentity.storyId = localIdentity.mediaId;
localIdentity.surface = 'story';
}
if (localIdentity.shortcode && !localIdentity.url) {
localIdentity.url = `https://www.instagram.com/reel/${localIdentity.shortcode}/`;
}
if (hasMediaFields && instagramIdentityKeys(localIdentity).length) {
const hasAudio = value.has_audio;
const versions = Array.isArray(value.video_versions)
? value.video_versions
: [];
for (const version of versions) {
if (!version?.url) {
continue;
}
rememberInstagramMediaSource(localIdentity, version.url, {
kind: 'direct',
source: `${source}-video_versions`,
contentType: 'video/mp4',
likelyMuxed: true,
width: Number(version.width || 0),
height: Number(version.height || 0),
bitrate: Number(version.bitrate || 0),
scoreBonus: 180000 + Number(version.height || 0) * 100
});
}
for (const [key, bonus] of [
['video_url', 165000],
['playback_url', 155000]
]) {
if (value[key]) {
rememberInstagramMediaSource(localIdentity, value[key], {
kind: 'direct',
source: `${source}-${key}`,
contentType: 'video/mp4',
likelyMuxed: true,
width: Number(value.original_width || value.dimensions?.width || 0),
height: Number(value.original_height || value.dimensions?.height || 0),
scoreBonus: bonus
});
}
}
}
for (const child of Object.values(value)) {
if (child && typeof child === 'object') {
extractInstagramMediaSourcesFromObject(
child,
localIdentity,
source,
depth + 1,
seen
);
}
}
}
function captureInstagramResponseText(text, source = 'instagram-network') {
const body = String(text || '').trim();
if (!body || body.length > 40 * 1024 * 1024) {
return;
}
const candidates = [body.replace(/^for\s*\(;;\)\s*;\s*/, '')];
if (body.includes('\n')) {
candidates.push(...body.split(/\r?\n/).slice(0, 500));
}
for (const candidate of candidates) {
try {
const parsed = JSON.parse(candidate);
captureInstagramStoryContextsFromObject(parsed);
extractInstagramMediaSourcesFromObject(parsed, {}, source);
} catch {
// Ignore non-JSON response chunks.
}
}
}
function captureInstagramResponseObject(value, source = 'instagram-network') {
try {
if (value && typeof value === 'object') {
captureInstagramStoryContextsFromObject(value);
extractInstagramMediaSourcesFromObject(value, {}, source);
}
} catch {
// Ignore malformed/cyclic response objects.
}
}
function shouldCaptureInstagramResponse(url, contentType = '') {
let host = '';
let path = '';
try {
const parsed = new URL(url, location.href);
host = parsed.hostname;
path = parsed.pathname.toLowerCase();
} catch {
return false;
}
return Boolean(
isInstagramHostname(host) &&
(
path.includes('/api/') ||
path.includes('/graphql') ||
String(contentType || '').toLowerCase().includes('json')
)
);
}
// ============================================================
// FACEBOOK DELIVERY RESPONSE CAPTURE (V9.7)
//
// Facebook Reels is a single-page feed. The visible reel can change while
// the document remains the same, so page-wide script scraping alone can
// keep returning the first reel. V10.0 captures delivery data from fresh
// GraphQL responses and stores it per Facebook video ID.
// ============================================================
function isFacebookHostname(hostname = location.hostname) {
const host = String(hostname || '').toLowerCase();
return (
host === 'facebook.com' ||
host.endsWith('.facebook.com') ||
host === 'fb.watch' ||
host.endsWith('.fb.watch')
);
}
function normalizeFacebookVideoId(value) {
const textValue = String(value || '').trim();
const match = textValue.match(/^\d{5,30}$/);
return match ? match[0] : '';
}
function inferFacebookHeight(value, fallbackQuality = '') {
const textValue = String(value || '');
const quality = String(fallbackQuality || '').toLowerCase();
const explicit = textValue.match(/(?:^|[^0-9])(\d{3,4})\s*p(?:[^0-9]|$)/i);
if (explicit) {
return Number(explicit[1]);
}
if (/\b(?:uhd|4k|2160)\b/i.test(textValue)) return 2160;
if (/\b(?:qhd|2k|1440)\b/i.test(textValue)) return 1440;
if (/\b1080\b/i.test(textValue)) return 1080;
if (/\b720\b/i.test(textValue)) return 720;
if (/\b480\b/i.test(textValue)) return 480;
if (/\b360\b/i.test(textValue)) return 360;
if (quality.includes('hd')) return 720;
if (quality.includes('sd')) return 360;
return 0;
}
function rememberFacebookVideoSource(videoId, url, details = {}) {
const id = normalizeFacebookVideoId(videoId);
const normalized = safeUrl(url);
if (
!id ||
!normalized ||
normalized.startsWith('data:') ||
isImageLikeUrl(normalized)
) {
return;
}
const kind = details.kind || classifyMediaUrl(
normalized,
details.contentType || '',
details.initiatorType || ''
);
if (!['direct', 'hls', 'dash'].includes(kind)) {
return;
}
const quality = String(details.quality || '').toLowerCase();
const width = Number(details.width || 0);
const height = Number(details.height || inferFacebookHeight(
`${details.quality || ''} ${normalized}`,
quality
));
const bitrate = Number(details.bitrate || 0);
const timestamp = nowMs();
const scoreBonus = Number(details.scoreBonus || 0);
let sources = facebookVideoSourceStore.get(id);
if (!sources) {
sources = new Map();
facebookVideoSourceStore.set(id, sources);
}
const previous = sources.get(normalized);
const record = {
url: normalized,
kind,
source: details.source || 'facebook-delivery',
contentType: details.contentType || previous?.contentType || '',
likelyMuxed: details.likelyMuxed !== false && kind === 'direct',
quality: quality || previous?.quality || '',
width: width || previous?.width || 0,
height: height || previous?.height || 0,
bitrate: bitrate || previous?.bitrate || 0,
scoreBonus: Math.max(scoreBonus, Number(previous?.scoreBonus || 0)),
timestamp
};
sources.set(normalized, record);
const urlKey = getInstagramUrlIndexKey(normalized);
if (urlKey) {
facebookUrlIdentityStore.set(urlKey, {
id,
timestamp
});
}
if (facebookUrlIdentityStore.size > 1500) {
const cutoff = nowMs() - CONFIG.mediaUrlMaxAgeMs;
for (const [key, item] of facebookUrlIdentityStore) {
if (
Number(item?.timestamp || 0) < cutoff ||
facebookUrlIdentityStore.size > 1100
) {
facebookUrlIdentityStore.delete(key);
}
}
}
// Keep the cache bounded. Reels can generate a lot of GraphQL data.
if (facebookVideoSourceStore.size > 180) {
const cutoff = nowMs() - CONFIG.mediaUrlMaxAgeMs;
for (const [storedId, storedSources] of facebookVideoSourceStore) {
let newest = 0;
for (const item of storedSources.values()) {
newest = Math.max(newest, Number(item.timestamp || 0));
}
if (newest < cutoff || facebookVideoSourceStore.size > 140) {
facebookVideoSourceStore.delete(storedId);
}
}
}
}
function facebookObjectHasDeliveryData(value) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return false;
}
const legacy = value.videoDeliveryLegacyFields;
const response = (
value.videoDeliveryResponseFragment?.videoDeliveryResponseResult ||
value.videoDeliveryResponseResult
);
return Boolean(
legacy ||
response ||
value.playable_url ||
value.playable_url_quality_hd ||
value.browser_native_hd_url ||
value.browser_native_sd_url ||
value.progressive_url ||
value.hls_playlist_url
);
}
function extractFacebookDeliverySourcesFromObject(
value,
inheritedVideoId = '',
source = 'facebook-graphql',
depth = 0
) {
if (
value === null ||
value === undefined ||
depth > 22
) {
return;
}
if (Array.isArray(value)) {
for (const item of value.slice(0, 2500)) {
extractFacebookDeliverySourcesFromObject(
item,
inheritedVideoId,
source,
depth + 1
);
}
return;
}
if (typeof value !== 'object') {
return;
}
const hasDelivery = facebookObjectHasDeliveryData(value);
let localVideoId = inheritedVideoId;
if (
hasDelivery ||
value.__typename === 'Video' ||
value.videoId ||
value.video_id
) {
localVideoId = normalizeFacebookVideoId(
value.videoId ||
value.video_id ||
(hasDelivery ? value.id : '') ||
inheritedVideoId
) || inheritedVideoId;
}
const legacy = value.videoDeliveryLegacyFields || value;
const legacyFields = [
['playable_url_quality_hd', 'hd', 190000],
['browser_native_hd_url', 'hd', 185000],
['playable_url', 'sd', 80000],
['browser_native_sd_url', 'sd', 76000]
];
if (localVideoId) {
for (const [key, quality, scoreBonus] of legacyFields) {
if (!legacy?.[key]) {
continue;
}
rememberFacebookVideoSource(
localVideoId,
legacy[key],
{
kind: 'direct',
source: `${source}-${key}`,
quality,
height: quality === 'hd' ? 720 : 360,
scoreBonus,
likelyMuxed: true
}
);
}
const legacyDashUrl = legacy?.playable_url_dash || legacy?.dash_manifest_url;
if (legacyDashUrl) {
rememberFacebookVideoSource(localVideoId, legacyDashUrl, {
kind: 'dash',
source: `${source}-legacy-dash`,
scoreBonus: 60000,
likelyMuxed: false
});
}
}
const deliveryResults = [
value.videoDeliveryResponseFragment?.videoDeliveryResponseResult,
value.videoDeliveryResponseResult
].filter(item => item && typeof item === 'object');
for (const delivery of deliveryResults) {
const responseVideoId = normalizeFacebookVideoId(
delivery.videoId ||
delivery.video_id ||
localVideoId
) || localVideoId;
if (!responseVideoId) {
continue;
}
for (const item of Array.isArray(delivery.progressive_urls)
? delivery.progressive_urls
: []) {
const url = item?.progressive_url;
if (!url) {
continue;
}
const metadata = item?.metadata || {};
const quality = String(
metadata.quality ||
item.quality ||
''
).toLowerCase();
const width = Number(metadata.width || item.width || 0);
const height = Number(
metadata.height ||
item.height ||
inferFacebookHeight(url, quality)
);
const bitrate = Number(
metadata.bitrate ||
item.bitrate ||
0
);
const isHd = quality.includes('hd') || height >= 720;
rememberFacebookVideoSource(responseVideoId, url, {
kind: 'direct',
source: `${source}-progressive-${quality || height || 'unknown'}`,
quality,
width,
height,
bitrate,
scoreBonus: (
200000 +
(isHd ? 90000 : 20000) +
Math.min(120000, height * 80)
),
likelyMuxed: true
});
}
for (const item of Array.isArray(delivery.hls_playlist_urls)
? delivery.hls_playlist_urls
: []) {
const url = item?.hls_playlist_url || item?.url;
if (url) {
rememberFacebookVideoSource(responseVideoId, url, {
kind: 'hls',
source: `${source}-hls`,
scoreBonus: 165000,
likelyMuxed: false
});
}
}
for (const item of Array.isArray(delivery.dash_manifest_urls)
? delivery.dash_manifest_urls
: []) {
const url = item?.manifest_url || item?.url;
if (url) {
rememberFacebookVideoSource(responseVideoId, url, {
kind: 'dash',
source: `${source}-dash`,
scoreBonus: 70000,
likelyMuxed: false
});
}
}
}
for (const child of Object.values(value)) {
if (child && typeof child === 'object') {
extractFacebookDeliverySourcesFromObject(
child,
localVideoId,
source,
depth + 1
);
}
}
}
function parseFacebookJsonPayload(text) {
let value = String(text || '').trim();
if (!value) {
return null;
}
value = value.replace(/^for\s*\(;;\)\s*;\s*/, '');
try {
return JSON.parse(value);
} catch {
return null;
}
}
function captureFacebookResponseText(text, source = 'facebook-network') {
const body = String(text || '');
if (!body || body.length > 32 * 1024 * 1024) {
return;
}
const whole = parseFacebookJsonPayload(body);
if (whole) {
extractFacebookDeliverySourcesFromObject(whole, '', source);
return;
}
// Facebook GraphQL can return multiple JSON payloads separated by newlines.
for (const line of body.split(/\r?\n/).slice(0, 400)) {
const parsed = parseFacebookJsonPayload(line);
if (parsed) {
extractFacebookDeliverySourcesFromObject(parsed, '', source);
}
}
}
function captureFacebookResponseObject(value, source = 'facebook-network') {
try {
if (value && typeof value === 'object') {
extractFacebookDeliverySourcesFromObject(value, '', source);
}
} catch {
// Ignore malformed/cyclic response objects.
}
}
function shouldCaptureFacebookResponse(url, contentType = '') {
let host = '';
let path = '';
try {
const parsed = new URL(url, location.href);
host = parsed.hostname;
path = parsed.pathname.toLowerCase();
} catch {
return false;
}
return Boolean(
isFacebookHostname(host) &&
(
path.includes('/api/graphql') ||
path.includes('/graphql') ||
String(contentType || '').toLowerCase().includes('json')
)
);
}
function installPerformanceCapture() {
try {
const captureEntry = entry => {
rememberMediaUrl(entry.name, {
source: 'performance',
initiatorType: entry.initiatorType || ''
});
};
for (const entry of performance.getEntriesByType('resource')) {
captureEntry(entry);
}
if (typeof PerformanceObserver === 'function') {
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
captureEntry(entry);
}
});
observer.observe({
type: 'resource',
buffered: true
});
}
} catch {
// Some sites may restrict the Performance API.
}
}
function installFetchCapture() {
try {
if (!PAGE.fetch || PAGE.fetch.__vrzV9Patched) {
return;
}
const originalFetch = PAGE.fetch;
const wrappedFetch = function (...args) {
let requestUrl = '';
try {
requestUrl = (
typeof args[0] === 'string'
? args[0]
: args[0]?.url
) || '';
rememberMediaUrl(requestUrl, {
source: 'fetch-request'
});
} catch {
// Ignore.
}
const result = Reflect.apply(originalFetch, this, args);
try {
return result.then(response => {
try {
const responseUrl = response?.url || requestUrl;
const contentType =
response?.headers?.get?.('content-type') || '';
rememberMediaUrl(
responseUrl,
{
source: 'fetch-response',
contentType
}
);
if (shouldCaptureFacebookResponse(responseUrl, contentType)) {
response.clone().text()
.then(body => {
captureFacebookResponseText(
body,
'facebook-fetch-graphql'
);
})
.catch(() => {
// Response bodies are best-effort only.
});
}
if (shouldCaptureInstagramResponse(responseUrl, contentType)) {
response.clone().text()
.then(body => {
captureInstagramResponseText(
body,
'instagram-fetch-json'
);
})
.catch(() => {
// Response bodies are best-effort only.
});
}
} catch {
// Ignore.
}
return response;
});
} catch {
return result;
}
};
Object.defineProperty(wrappedFetch, '__vrzV9Patched', {
value: true
});
PAGE.fetch = wrappedFetch;
} catch {
// Some sites lock the fetch function.
}
}
function installXhrCapture() {
try {
const proto = PAGE.XMLHttpRequest?.prototype;
if (!proto || proto.open?.__vrzV9Patched) {
return;
}
const originalOpen = proto.open;
const originalSend = proto.send;
const wrappedOpen = function (method, url, ...rest) {
try {
this.__vrzV9Url = safeUrl(url);
rememberMediaUrl(this.__vrzV9Url, {
source: 'xhr-request'
});
} catch {
// Ignore.
}
return Reflect.apply(
originalOpen,
this,
[method, url, ...rest]
);
};
const wrappedSend = function (...args) {
try {
this.addEventListener('load', () => {
try {
const responseUrl = this.responseURL || this.__vrzV9Url;
const contentType =
this.getResponseHeader?.('content-type') || '';
rememberMediaUrl(
responseUrl,
{
source: 'xhr-response',
contentType
}
);
if (shouldCaptureFacebookResponse(responseUrl, contentType)) {
if (
this.responseType === 'json' &&
this.response &&
typeof this.response === 'object'
) {
captureFacebookResponseObject(
this.response,
'facebook-xhr-graphql'
);
} else {
let responseText = '';
try {
responseText = this.responseText || '';
} catch {
responseText = '';
}
if (responseText) {
captureFacebookResponseText(
responseText,
'facebook-xhr-graphql'
);
}
}
}
if (shouldCaptureInstagramResponse(responseUrl, contentType)) {
if (
this.responseType === 'json' &&
this.response &&
typeof this.response === 'object'
) {
captureInstagramResponseObject(
this.response,
'instagram-xhr-json'
);
} else {
let responseText = '';
try {
responseText = this.responseText || '';
} catch {
responseText = '';
}
if (responseText) {
captureInstagramResponseText(
responseText,
'instagram-xhr-json'
);
}
}
}
} catch {
// Ignore.
}
}, { once: true });
} catch {
// Ignore.
}
return Reflect.apply(originalSend, this, args);
};
Object.defineProperty(wrappedOpen, '__vrzV9Patched', {
value: true
});
proto.open = wrappedOpen;
proto.send = wrappedSend;
} catch {
// Some sites lock the XHR prototype.
}
}
function installShadowCapture() {
try {
const proto = PAGE.Element?.prototype;
if (!proto?.attachShadow || proto.attachShadow.__vrzV9Patched) {
return;
}
const originalAttachShadow = proto.attachShadow;
const wrappedAttachShadow = function (init) {
const shadowRoot = Reflect.apply(
originalAttachShadow,
this,
[init]
);
try {
capturedShadowRoots.add(shadowRoot);
if (uiStarted) {
externalScheduleScan?.();
}
} catch {
// Ignore.
}
return shadowRoot;
};
Object.defineProperty(wrappedAttachShadow, '__vrzV9Patched', {
value: true
});
proto.attachShadow = wrappedAttachShadow;
} catch {
// Some closed or locked prototypes cannot be patched.
}
}
installHlsInstanceCapture();
installFetchCapture();
installXhrCapture();
installShadowCapture();
installPerformanceCapture();
// ============================================================
// START AFTER THE DOCUMENT ELEMENT IS AVAILABLE
// ============================================================
function bootWhenReady() {
if (document.documentElement) {
startMain();
return;
}
const observer = new MutationObserver(() => {
if (document.documentElement) {
observer.disconnect();
startMain();
}
});
observer.observe(document, {
childList: true,
subtree: true
});
}
function startMain() {
if (uiStarted) {
return;
}
ROOT = document.documentElement;
if (!ROOT || ROOT.hasAttribute('data-vrz-v104-active')) {
return;
}
ROOT.setAttribute('data-vrz-v104-active', '1');
uiStarted = true;
// ========================================================
// UI / VIDEO STATE
// ========================================================
let activeVideo = null;
let transformedVideo = null;
let currentContainer = null;
let angle = 0;
let zoom = 1;
// Hand Tool pan offsets are stored in viewport pixels so dragging
// always follows the pointer direction, even after a 90° rotation.
let panX = 0;
let panY = 0;
let handToolActive = false;
let handTransformFrame = null;
// AUTO ROTATE DEFAULT OFF.
let autoRotate = false;
let panelMinimized = false;
let panelVisible = false;
let statusTimer = null;
let resizeTimer = null;
let scanTimer = null;
let fullscreenIdleTimer = null;
let directFullscreenRepairing = false;
let hostWasDragged = false;
let downloadAttemptActive = false;
let qualityMenuOpen = false;
let qualityMenuRequestId = 0;
let activeQualityAdapter = null;
let preferredResolutionHeight = 0;
let resolutionVideo = null;
const registeredVideos = new WeakSet();
const observedRoots = new WeakSet();
const originalVideoStyles = new WeakMap();
const originalVideoComputedTransforms = new WeakMap();
const originalContainerStyles = new WeakMap();
const discoveredShadowRoots = new Set();
// ========================================================
// REMOVE / HIDE PANELS FROM OLDER VERSIONS
// ========================================================
const legacyBlocker = document.createElement('style');
legacyBlocker.id = 'vrz-v104-legacy-blocker';
legacyBlocker.textContent = `
#tm-video-rotator-panel,
#tm-video-tools-panel,
#vrz-v6-host,
#vrz-v7-host,
#vrz-v8-host,
#vrz-v9-host,
#vrz-v91-host,
#vrz-v92-host,
#vrz-v93-host,
#vrz-v94-host,
#vrz-v95-host,
#vrz-v96-host,
#vrz-v97-host,
#vrz-v98-host,
#vrz-v99-host,
#vrz-v100-host,
#vrz-v101-host,
#vrz-v102-host,
#vrz-v103-host {
display: none !important;
visibility: hidden !important;
pointer-events: none !important;
}
`;
ROOT.appendChild(legacyBlocker);
// ========================================================
// PROPERTIES MODIFIED BY THIS SCRIPT
// ========================================================
// V9.7 intentionally modifies only transform-related video styles.
// Keeping the site's own position/size/pointer-events prevents complex
// players (especially YouTube) from losing their layout or click-to-pause.
const VIDEO_PROPERTIES = [
'transform',
'transform-origin',
'translate',
'rotate',
'scale',
'will-change'
];
const CONTAINER_PROPERTIES = [
'overflow'
];
// ========================================================
// BROADER VIDEO DETECTION
// ========================================================
function getRootElements(root) {
try {
return root.querySelectorAll('*');
} catch {
return [];
}
}
function discoverShadowRoots(root) {
const queue = [root];
const seen = new Set();
for (const captured of capturedShadowRoots) {
if (captured?.isConnected !== false) {
discoveredShadowRoots.add(captured);
}
}
while (queue.length) {
const currentRoot = queue.shift();
if (!currentRoot || seen.has(currentRoot)) {
continue;
}
seen.add(currentRoot);
for (const element of getRootElements(currentRoot)) {
const shadowRoot = element.shadowRoot;
if (shadowRoot && !seen.has(shadowRoot)) {
discoveredShadowRoots.add(shadowRoot);
queue.push(shadowRoot);
}
}
}
}
function getAllSearchRoots() {
discoverShadowRoots(document);
return [
document,
...discoveredShadowRoots,
...capturedShadowRoots
].filter(Boolean);
}
function getAllVideos() {
const result = [];
const seen = new Set();
for (const root of getAllSearchRoots()) {
let videos = [];
try {
videos = root.querySelectorAll('video');
} catch {
videos = [];
}
for (const video of videos) {
if (!seen.has(video)) {
seen.add(video);
result.push(video);
}
}
}
return result;
}
function isHiddenByAncestors(video) {
let element = video;
let depth = 0;
while (
element &&
element.nodeType === Node.ELEMENT_NODE &&
depth < 16
) {
let style;
try {
style = getComputedStyle(element);
} catch {
return true;
}
if (
style.display === 'none' ||
style.visibility === 'hidden' ||
style.visibility === 'collapse' ||
Number.parseFloat(style.opacity || '1') <= 0.01
) {
return true;
}
const rootNode = element.getRootNode?.();
if (
rootNode instanceof ShadowRoot &&
element.parentElement === null
) {
element = rootNode.host;
} else {
element = element.parentElement;
}
depth += 1;
}
return false;
}
function getVisibleVideoMetrics(video) {
if (!isElementConnected(video)) {
return null;
}
let rect;
try {
rect = video.getBoundingClientRect();
} catch {
return null;
}
if (
!Number.isFinite(rect.width) ||
!Number.isFinite(rect.height) ||
rect.width <= 1 ||
rect.height <= 1 ||
video.getClientRects().length === 0 ||
isHiddenByAncestors(video)
) {
return null;
}
const visibleLeft = Math.max(0, rect.left);
const visibleTop = Math.max(0, rect.top);
const visibleRight = Math.min(window.innerWidth, rect.right);
const visibleBottom = Math.min(window.innerHeight, rect.bottom);
const visibleWidth = Math.max(0, visibleRight - visibleLeft);
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
const visibleArea = visibleWidth * visibleHeight;
const isPlaying = !video.paused && !video.ended;
if (
visibleWidth < CONFIG.minVisiblePixels ||
visibleHeight < CONFIG.minVisiblePixels
) {
return null;
}
if (
visibleArea < CONFIG.minLikelyVideoArea &&
!(isPlaying && visibleArea >= CONFIG.minPlayingVideoArea)
) {
return null;
}
return {
rect,
visibleWidth,
visibleHeight,
visibleArea
};
}
function isVideoVisible(video) {
return Boolean(getVisibleVideoMetrics(video));
}
function scoreVideo(video) {
const metrics = getVisibleVideoMetrics(video);
if (!metrics) {
return -Infinity;
}
let score = metrics.visibleArea;
if (!video.paused && !video.ended) {
score += 5_000_000_000;
}
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
score += 500_000_000;
}
if (video.currentTime > 0) {
score += 250_000_000;
}
if (video.duration > 30 || !Number.isFinite(video.duration)) {
score += 120_000_000;
}
if (video.muted === false && video.volume > 0) {
score += 50_000_000;
}
if (video === activeVideo) {
score += 750_000_000;
}
return score;
}
function findBestVideo() {
const videos = getAllVideos();
if (!videos.length) {
return null;
}
let bestVideo = null;
let bestScore = -Infinity;
for (const video of videos) {
const score = scoreVideo(video);
if (score > bestScore) {
bestScore = score;
bestVideo = video;
}
}
return bestScore > -Infinity ? bestVideo : null;
}
function getVideo() {
// While a transform is active, keep targeting the same element even
// if its transformed bounding box temporarily falls outside the
// viewport. This prevents YouTube from losing the active video after
// repeated rotate / zoom operations.
if (
transformedVideo &&
transformedVideo.isConnected &&
(angle !== 0 || zoom !== 1 || panX !== 0 || panY !== 0)
) {
activeVideo = transformedVideo;
return transformedVideo;
}
if (
activeVideo &&
isVideoVisible(activeVideo)
) {
return activeVideo;
}
activeVideo = findBestVideo();
return activeVideo;
}
// ========================================================
// FRESH DOWNLOAD TARGET SELECTION
//
// Facebook Reels reuses one SPA document while the user scrolls. The
// transform subsystem intentionally keeps a transformed video locked,
// but downloads must always resolve the reel that is visible NOW.
// ========================================================
function getDownloadForegroundScore(video) {
const metrics = getVisibleVideoMetrics(video);
if (!metrics) {
return 0;
}
const rect = metrics.rect;
const left = Math.max(0, rect.left);
const right = Math.min(window.innerWidth, rect.right);
const top = Math.max(0, rect.top);
const bottom = Math.min(window.innerHeight, rect.bottom);
if (right <= left || bottom <= top) {
return 0;
}
const samplePoints = [
[0.50, 0.50],
[0.25, 0.25],
[0.75, 0.25],
[0.25, 0.75],
[0.75, 0.75],
[0.50, 0.20],
[0.50, 0.80]
];
const container = (
video.closest?.(
'article, [role="dialog"], [aria-modal="true"], [role="presentation"]'
) ||
video.parentElement
);
let visibleSamples = 0;
for (const [rx, ry] of samplePoints) {
const x = Math.floor(left + (right - left) * rx);
const y = Math.floor(top + (bottom - top) * ry);
let stack = [];
try {
stack = document.elementsFromPoint(x, y);
} catch {
stack = [];
}
if (!stack.length) {
continue;
}
const videoIndex = stack.indexOf(video);
if (videoIndex < 0) {
continue;
}
let blockedByDifferentSurface = false;
for (let index = 0; index < videoIndex; index += 1) {
const element = stack[index];
const elementId = String(element?.id || '');
if (/^vrz-v9/i.test(elementId)) {
continue;
}
if (
container &&
(
container === element ||
container.contains?.(element)
)
) {
continue;
}
blockedByDifferentSurface = true;
break;
}
if (!blockedByDifferentSurface) {
visibleSamples += 1;
}
}
return visibleSamples / samplePoints.length;
}
function scoreFreshDownloadVideo(video, platform = '') {
const metrics = getVisibleVideoMetrics(video);
if (!metrics) {
return -Infinity;
}
const foregroundScore = getDownloadForegroundScore(video);
// Instagram/Facebook SPAs often leave old Story/Reel videos mounted
// and sometimes even playing behind the current surface. Never use
// those stale elements as download targets.
if (
(platform === 'instagram' || platform === 'facebook') &&
foregroundScore <= 0
) {
return -Infinity;
}
const rect = metrics.rect;
const viewportCenterX = window.innerWidth / 2;
const viewportCenterY = window.innerHeight / 2;
const videoCenterX = rect.left + rect.width / 2;
const videoCenterY = rect.top + rect.height / 2;
const centerDistance = Math.hypot(
videoCenterX - viewportCenterX,
videoCenterY - viewportCenterY
);
const totalArea = Math.max(1, rect.width * rect.height);
const visibleRatio = clamp(metrics.visibleArea / totalArea, 0, 1);
const containsViewportCenter = Boolean(
rect.left <= viewportCenterX &&
rect.right >= viewportCenterX &&
rect.top <= viewportCenterY &&
rect.bottom >= viewportCenterY
);
let score = metrics.visibleArea;
score += visibleRatio * 2_000_000_000;
score += foregroundScore * 12_000_000_000;
score += Math.max(0, 1_800_000_000 - centerDistance * 1_500_000);
if (containsViewportCenter) {
score += 4_000_000_000;
}
if (!video.paused && !video.ended) {
score += 6_000_000_000;
}
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
score += 700_000_000;
}
if (video.currentTime > 0) {
score += 300_000_000;
}
if (video === transformedVideo) {
score += 250_000_000;
}
return score;
}
function findFreshDownloadVideo(platform = '') {
let bestVideo = null;
let bestScore = -Infinity;
for (const video of getAllVideos()) {
const score = scoreFreshDownloadVideo(video, platform);
if (score > bestScore) {
bestScore = score;
bestVideo = video;
}
}
if (bestVideo) {
return bestVideo;
}
// V10.0 fallback: Instagram/Facebook sometimes place transparent
// controls, canvases, or overlay buttons above the real <video>.
// elementsFromPoint can then say the video is not foreground even
// though it is the video the user is seeing. For downloads only,
// fall back to the most visible media element instead of reusing a
// stale transformed/Story target.
if (platform === 'instagram' || platform === 'facebook') {
let relaxedVideo = null;
let relaxedScore = -Infinity;
for (const video of getAllVideos()) {
const metrics = getVisibleVideoMetrics(video);
if (!metrics) {
continue;
}
const rect = metrics.rect;
const viewportCenterX = window.innerWidth / 2;
const viewportCenterY = window.innerHeight / 2;
const videoCenterX = rect.left + rect.width / 2;
const videoCenterY = rect.top + rect.height / 2;
const distance = Math.hypot(
videoCenterX - viewportCenterX,
videoCenterY - viewportCenterY
);
const visibleRatio = clamp(
metrics.visibleArea / Math.max(1, rect.width * rect.height),
0,
1
);
let score = metrics.visibleArea + visibleRatio * 1_500_000_000;
score += Math.max(0, 1_200_000_000 - distance * 1_200_000);
if (!video.paused && !video.ended) {
score += 4_000_000_000;
}
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
score += 350_000_000;
}
if (score > relaxedScore) {
relaxedScore = score;
relaxedVideo = video;
}
}
return relaxedVideo;
}
return null;
}
function getVideoForDownload(platform) {
if (platform === 'instagram') {
const freshVideo = findFreshDownloadVideo('instagram');
if (freshVideo) {
return freshVideo;
}
// V10.4: restore the V9.6 compatibility fallback only for
// Home/Explore/Profile/post surfaces. The Story route remains
// strict because transform state or a mounted previous Story
// must never become the download target.
const storyRoute = extractInstagramStoryIdentityFromUrl(location.href);
if (storyRoute?.storyId || storyRoute?.surface === 'story') {
return null;
}
return getVideo();
}
if (platform === 'facebook') {
return findFreshDownloadVideo('facebook');
}
return getVideo();
}
// ========================================================
// PLAYER / CONTAINER DETECTION
// ========================================================
function getElementIdentity(element) {
if (!element) {
return '';
}
const id = element.id || '';
let className = '';
try {
if (
element.className &&
typeof element.className === 'object'
) {
className = element.className.baseVal || '';
} else {
className = element.className || '';
}
} catch {
className = '';
}
const role = element.getAttribute?.('role') || '';
const aria = element.getAttribute?.('aria-label') || '';
return `${id} ${className} ${role} ${aria}`.toLowerCase();
}
function isLikelyPlayerIdentity(identity) {
return (
identity.includes('video-player') ||
identity.includes('video_player') ||
identity.includes('video-wrapper') ||
identity.includes('video-container') ||
identity.includes('media-player') ||
identity.includes('html5-video') ||
identity.includes('player-container') ||
identity.includes('player-wrapper') ||
identity.includes('jwplayer') ||
identity.includes('jw-') ||
identity.includes('videojs') ||
identity.includes('video-js') ||
identity.includes('vjs-') ||
identity.includes('plyr') ||
identity.includes('shaka') ||
identity.includes('dplayer') ||
identity.includes('artplayer') ||
identity.includes('mediaelement') ||
identity.includes('clappr') ||
identity.includes('flowplayer') ||
identity.includes('media-control')
);
}
function getComposedParent(element) {
if (!element) {
return null;
}
if (element.parentElement) {
return element.parentElement;
}
const rootNode = element.getRootNode?.();
if (rootNode instanceof ShadowRoot) {
return rootNode.host;
}
return null;
}
function getFullscreenElement() {
return (
document.fullscreenElement ||
document.webkitFullscreenElement ||
null
);
}
function findPlayerContainer(video, options = {}) {
const fullscreenElement = getFullscreenElement();
if (
!options.ignoreFullscreen &&
fullscreenElement &&
fullscreenElement !== video &&
fullscreenElement.contains?.(video)
) {
return fullscreenElement;
}
const videoRect = video.getBoundingClientRect();
let element = getComposedParent(video);
let best = element || video.parentElement;
let bestScore = -Infinity;
let depth = 0;
while (
element &&
element !== document.body &&
element !== ROOT &&
depth < 20
) {
let rect;
try {
rect = element.getBoundingClientRect();
} catch {
break;
}
const identity = getElementIdentity(element);
const largeEnough = (
rect.width >= Math.max(160, videoRect.width * 0.65) &&
rect.height >= Math.max(90, videoRect.height * 0.65)
);
if (largeEnough) {
const area = Math.max(1, rect.width * rect.height);
const videoArea = Math.max(1, videoRect.width * videoRect.height);
const ratio = area / videoArea;
let score = 0;
if (isLikelyPlayerIdentity(identity)) {
score += 20_000;
}
if (ratio >= 0.7 && ratio <= 8) {
score += 5_000;
}
if (
rect.width < window.innerWidth * 0.995 ||
rect.height < window.innerHeight * 0.98
) {
score += 1_500;
}
score -= depth * 15;
if (score > bestScore) {
bestScore = score;
best = element;
}
}
if (
rect.width >= window.innerWidth * 0.998 &&
rect.height >= window.innerHeight * 0.985 &&
!isLikelyPlayerIdentity(identity)
) {
break;
}
element = getComposedParent(element);
depth += 1;
}
return best || video.parentElement;
}
// ========================================================
// REDIRECT DIRECT VIDEO FULLSCREEN TO THE PLAYER CONTAINER
//
// This keeps the tools overlay inside the fullscreen subtree
// so it does not disappear when a site fullscreenes the
// <video> element directly.
// ========================================================
function installFullscreenRedirect() {
try {
const proto = PAGE.Element?.prototype;
if (
!proto?.requestFullscreen ||
proto.requestFullscreen.__vrzV9Patched
) {
return;
}
const originalRequestFullscreen = proto.requestFullscreen;
const wrappedRequestFullscreen = function (...args) {
try {
const isVideo = (
PAGE.HTMLVideoElement &&
this instanceof PAGE.HTMLVideoElement
);
if (isVideo) {
const target = findPlayerContainer(this, {
ignoreFullscreen: true
});
if (
target &&
target !== this &&
target.isConnected
) {
return Reflect.apply(
originalRequestFullscreen,
target,
args
);
}
}
} catch {
// Fall back to the original request.
}
return Reflect.apply(
originalRequestFullscreen,
this,
args
);
};
Object.defineProperty(
wrappedRequestFullscreen,
'__vrzV9Patched',
{ value: true }
);
proto.requestFullscreen = wrappedRequestFullscreen;
} catch {
// The site or browser may lock the fullscreen prototype.
}
try {
const videoProto = PAGE.HTMLVideoElement?.prototype;
if (
!videoProto?.webkitEnterFullscreen ||
videoProto.webkitEnterFullscreen.__vrzV9Patched
) {
return;
}
const originalWebkitEnter = videoProto.webkitEnterFullscreen;
const wrappedWebkitEnter = function (...args) {
try {
const target = findPlayerContainer(this, {
ignoreFullscreen: true
});
if (
target &&
target !== this &&
typeof target.requestFullscreen === 'function'
) {
return target.requestFullscreen();
}
} catch {
// Fall back to native fullscreen.
}
return Reflect.apply(originalWebkitEnter, this, args);
};
Object.defineProperty(
wrappedWebkitEnter,
'__vrzV9Patched',
{ value: true }
);
videoProto.webkitEnterFullscreen = wrappedWebkitEnter;
} catch {
// Ignore.
}
}
installFullscreenRedirect();
// ========================================================
// SAVE / RESTORE STYLES
// ========================================================
function saveVideoOriginalStyle(video) {
if (!originalVideoStyles.has(video)) {
originalVideoStyles.set(
video,
saveStyles(video, VIDEO_PROPERTIES)
);
try {
const computedTransform = getComputedStyle(video).transform;
originalVideoComputedTransforms.set(
video,
computedTransform && computedTransform !== 'none'
? computedTransform
: ''
);
} catch {
originalVideoComputedTransforms.set(video, '');
}
}
}
function saveContainerOriginalStyle(container) {
if (
container &&
!originalContainerStyles.has(container)
) {
originalContainerStyles.set(
container,
saveStyles(container, CONTAINER_PROPERTIES)
);
}
}
function restoreVideo(video) {
if (!video) {
return;
}
const original = originalVideoStyles.get(video);
if (original) {
restoreStyles(video, original);
}
}
function restoreContainer(container) {
if (!container) {
return;
}
const original = originalContainerStyles.get(container);
if (original) {
restoreStyles(container, original);
}
}
function restoreAllStyles() {
if (transformedVideo) {
restoreVideo(transformedVideo);
}
if (currentContainer) {
restoreContainer(currentContainer);
}
transformedVideo = null;
currentContainer = null;
}
// ========================================================
// KEEP VIDEO PLAYING DURING ROTATE / ZOOM
// ========================================================
function capturePlaybackState(video) {
if (!video) {
return null;
}
return {
wasPlaying: !video.paused && !video.ended,
playbackRate: video.playbackRate,
muted: video.muted,
volume: video.volume
};
}
function restorePlaybackIfNeeded(video, state) {
if (
!video ||
!state ||
!state.wasPlaying ||
!video.isConnected ||
video.ended
) {
return;
}
try {
if (video.playbackRate !== state.playbackRate) {
video.playbackRate = state.playbackRate;
}
if (video.muted !== state.muted) {
video.muted = state.muted;
}
if (Math.abs(video.volume - state.volume) > 0.01) {
video.volume = state.volume;
}
} catch {
// Some sites lock media properties.
}
if (video.paused) {
try {
const playPromise = video.play();
playPromise?.catch?.(() => {
// Autoplay policy may block this; later retries
// will still run from the next timers.
});
} catch {
// Ignore.
}
}
}
function schedulePlaybackRestore(video, state) {
if (!state?.wasPlaying) {
return;
}
for (const wait of CONFIG.playbackRestoreDelays) {
setTimeout(
() => restorePlaybackIfNeeded(video, state),
wait
);
}
}
// ========================================================
// V9.7 YOUTUBE-SAFE ROTATE + ZOOM + PAN
//
// YouTube is handled with the classic `transform` property instead
// of CSS individual rotate/scale properties. This avoids the black /
// disappearing video bug seen after repeated rotate and zoom actions
// in Chromium-based browsers while keeping the site's layout intact.
// Other players keep the safer individual-transform path when the
// browser supports it.
// ========================================================
const SUPPORTS_INDIVIDUAL_TRANSFORMS = (() => {
try {
return Boolean(
CSS?.supports?.('translate', '0px 0px') &&
CSS?.supports?.('rotate', '90deg') &&
CSS?.supports?.('scale', '1')
);
} catch {
return false;
}
})();
function isYouTubeVideo(video) {
if (!video) {
return false;
}
const hostname = location.hostname.toLowerCase();
const isYouTubeHost = (
hostname === 'youtube.com' ||
hostname.endsWith('.youtube.com') ||
hostname === 'youtube-nocookie.com' ||
hostname.endsWith('.youtube-nocookie.com')
);
if (!isYouTubeHost) {
return false;
}
try {
return Boolean(
video.classList?.contains('html5-main-video') ||
video.closest?.('#movie_player, ytd-player, .html5-video-player')
);
} catch {
return true;
}
}
function findTransformContainer(video) {
if (!video) {
return null;
}
if (isYouTubeVideo(video)) {
try {
const candidates = [
video.closest?.('.html5-video-container'),
video.closest?.('.html5-video-player'),
video.closest?.('#movie_player')
].filter(Boolean);
for (const candidate of candidates) {
const rect = candidate.getBoundingClientRect();
if (rect.width > 100 && rect.height > 60) {
return candidate;
}
}
} catch {
// Continue with universal detection.
}
}
try {
const visualViewport = video.closest?.(
'.jw-media, .video-js, .vjs-player, .plyr__video-wrapper, ' +
'.shaka-video-container, .dplayer-video-wrap, ' +
'.art-video-player, .media-wrapper'
);
if (visualViewport) {
return visualViewport;
}
} catch {
// Continue with universal detection.
}
return findPlayerContainer(video);
}
function restoreSavedVideoProperty(video, property) {
const saved = originalVideoStyles.get(video)?.[property];
if (!saved) {
video.style.removeProperty(property);
return;
}
if (saved.value) {
video.style.setProperty(
property,
saved.value,
saved.priority
);
} else {
video.style.removeProperty(property);
}
}
function measureVideoWithoutToolTransform(video) {
const currentStyles = saveStyles(video, VIDEO_PROPERTIES);
const originalStyles = originalVideoStyles.get(video);
try {
if (originalStyles) {
restoreStyles(video, originalStyles);
}
const rect = video.getBoundingClientRect();
let baseTransform = '';
try {
const computedTransform = getComputedStyle(video).transform;
baseTransform = (
computedTransform && computedTransform !== 'none'
? computedTransform
: ''
);
} catch {
baseTransform = (
originalVideoComputedTransforms.get(video) || ''
);
}
return {
left: rect.left,
top: rect.top,
width: Math.max(1, rect.width),
height: Math.max(1, rect.height),
baseTransform
};
} finally {
restoreStyles(video, currentStyles);
}
}
function calculateTransformScale(videoRect, containerRect) {
const sideways = angle === 90 || angle === 270;
if (!sideways) {
return zoom;
}
const rotatedWidth = Math.max(1, videoRect.height);
const rotatedHeight = Math.max(1, videoRect.width);
const fitScale = Math.min(
Math.max(1, containerRect.width) / rotatedWidth,
Math.max(1, containerRect.height) / rotatedHeight
);
const safeFitScale = (
Number.isFinite(fitScale) && fitScale > 0
? clamp(fitScale, 0.05, 20)
: 1
);
return safeFitScale * zoom;
}
function getPanBounds(videoRect, containerRect, finalScale) {
const sideways = angle === 90 || angle === 270;
const visualWidth = (
sideways ? videoRect.height : videoRect.width
) * finalScale;
const visualHeight = (
sideways ? videoRect.width : videoRect.height
) * finalScale;
return {
maxX: Math.max(
0,
(visualWidth - Math.max(1, containerRect.width)) / 2
),
maxY: Math.max(
0,
(visualHeight - Math.max(1, containerRect.height)) / 2
)
};
}
function clampPanForGeometry(videoRect, containerRect, finalScale) {
const bounds = getPanBounds(
videoRect,
containerRect,
finalScale
);
panX = clamp(panX, -bounds.maxX, bounds.maxX);
panY = clamp(panY, -bounds.maxY, bounds.maxY);
if (Math.abs(panX) < 0.01) {
panX = 0;
}
if (Math.abs(panY) < 0.01) {
panY = 0;
}
return bounds;
}
function applyVisualTransform(
video,
container,
videoRect,
containerRect,
finalScale
) {
clampPanForGeometry(videoRect, containerRect, finalScale);
setImportant(container, 'overflow', 'hidden');
setImportant(video, 'transform-origin', 'center center');
if (isYouTubeVideo(video)) {
// IMPORTANT: Do not use individual rotate/scale on YouTube.
// Repeated changes can make Chromium's hardware video layer
// render black or disappear. A single classic transform is much
// more stable and keeps native click-to-pause intact.
setImportant(video, 'translate', 'none');
setImportant(video, 'rotate', 'none');
setImportant(video, 'scale', 'none');
restoreSavedVideoProperty(video, 'will-change');
const toolTransform = [
(panX !== 0 || panY !== 0)
? `translate(${panX}px, ${panY}px)`
: '',
`rotate(${angle}deg)`,
`scale(${finalScale})`
].filter(Boolean).join(' ');
setImportant(
video,
'transform',
[
videoRect.baseTransform,
toolTransform
].filter(Boolean).join(' ')
);
return;
}
if (SUPPORTS_INDIVIDUAL_TRANSFORMS) {
// Keep the website's own transform untouched on normal players.
restoreSavedVideoProperty(video, 'transform');
setImportant(video, 'translate', `${panX}px ${panY}px`);
setImportant(video, 'rotate', `${angle}deg`);
setImportant(video, 'scale', `${finalScale}`);
setImportant(video, 'will-change', 'translate, rotate, scale');
return;
}
const baseTransform = (
videoRect.baseTransform ||
originalVideoComputedTransforms.get(video) ||
''
);
setImportant(video, 'translate', 'none');
setImportant(video, 'rotate', 'none');
setImportant(video, 'scale', 'none');
setImportant(video, 'will-change', 'transform');
setImportant(
video,
'transform',
[
baseTransform,
(panX !== 0 || panY !== 0)
? `translate(${panX}px, ${panY}px)`
: '',
`rotate(${angle}deg)`,
`scale(${finalScale})`
].filter(Boolean).join(' ')
);
}
function scheduleTransformStabilization(video) {
if (!isYouTubeVideo(video)) {
return;
}
for (const wait of [60, 180, 500]) {
setTimeout(() => {
if (
transformedVideo !== video ||
!video.isConnected ||
(angle === 0 && zoom === 1 && panX === 0 && panY === 0)
) {
return;
}
applyTransform({
preservePlayback: true,
skipStabilize: true
});
}, wait);
}
}
// ========================================================
// APPLY ROTATE + ZOOM + PAN
// ========================================================
function applyTransform(options = {}) {
const video = getVideo();
if (!video) {
restoreAllStyles();
updatePanelVisibility();
syncHandOverlay();
return;
}
const playbackState = (
options.preservePlayback === false
? null
: capturePlaybackState(video)
);
angle = normalizeAngle(angle);
zoom = clamp(zoom, CONFIG.minZoom, CONFIG.maxZoom);
if (
angle === 0 &&
zoom === 1 &&
panX === 0 &&
panY === 0
) {
restoreAllStyles();
updateStatus();
syncHandOverlay();
schedulePlaybackRestore(video, playbackState);
return;
}
if (
transformedVideo &&
transformedVideo !== video
) {
restoreVideo(transformedVideo);
transformedVideo = null;
panX = 0;
panY = 0;
}
saveVideoOriginalStyle(video);
const container = findTransformContainer(video);
if (!container) {
showMessage('Player not found');
schedulePlaybackRestore(video, playbackState);
return;
}
if (
currentContainer &&
currentContainer !== container
) {
restoreContainer(currentContainer);
}
transformedVideo = video;
activeVideo = video;
currentContainer = container;
saveContainerOriginalStyle(container);
const videoRect = measureVideoWithoutToolTransform(video);
const containerRect = container.getBoundingClientRect();
if (
containerRect.width <= 1 ||
containerRect.height <= 1
) {
showMessage('Player geometry unavailable');
schedulePlaybackRestore(video, playbackState);
return;
}
const finalScale = calculateTransformScale(
videoRect,
containerRect
);
applyVisualTransform(
video,
container,
videoRect,
containerRect,
finalScale
);
updateStatus();
syncHandOverlay();
schedulePlaybackRestore(video, playbackState);
if (!options.skipStabilize) {
scheduleTransformStabilization(video);
}
}
// ========================================================
// ROTATE / ZOOM / RESET
// ========================================================
function rotateLeft() {
autoRotate = false;
panX = 0;
panY = 0;
angle -= 90;
applyTransform({ preservePlayback: true });
}
function rotateRight() {
autoRotate = false;
panX = 0;
panY = 0;
angle += 90;
applyTransform({ preservePlayback: true });
}
function zoomIn() {
zoom = Math.round((zoom + CONFIG.zoomStep) * 100) / 100;
applyTransform({ preservePlayback: true });
}
function zoomOut() {
zoom = Math.round((zoom - CONFIG.zoomStep) * 100) / 100;
applyTransform({ preservePlayback: true });
}
function resetZoom() {
zoom = 1;
panX = 0;
panY = 0;
applyTransform({ preservePlayback: true });
}
function resetEverything() {
const video = getVideo();
const playbackState = capturePlaybackState(video);
autoRotate = false;
angle = 0;
zoom = 1;
panX = 0;
panY = 0;
preferredResolutionHeight = 0;
setHandToolActive(false, { silent: true });
closeResolutionMenu();
restoreAllStyles();
updateStatus();
updateResolutionButton(video);
schedulePlaybackRestore(video, playbackState);
}
// ========================================================
// AUTO ROTATE (DEFAULT OFF)
// ========================================================
function tryAutoRotate(video) {
if (
!autoRotate ||
!video ||
video.videoWidth <= 0 ||
video.videoHeight <= 0
) {
return;
}
if (video.videoHeight > video.videoWidth) {
activeVideo = video;
if (angle === 0) {
angle = 90;
applyTransform({ preservePlayback: true });
}
}
}
// ========================================================
// FULLSCREEN
// ========================================================
async function requestElementFullscreen(element) {
if (!element) {
throw new Error('Fullscreen element is unavailable');
}
if (element.requestFullscreen) {
return element.requestFullscreen();
}
if (element.webkitRequestFullscreen) {
return element.webkitRequestFullscreen();
}
throw new Error('Fullscreen API is not supported');
}
async function exitCurrentFullscreen() {
if (document.exitFullscreen) {
return document.exitFullscreen();
}
if (document.webkitExitFullscreen) {
return document.webkitExitFullscreen();
}
}
async function toggleFullscreen() {
const video = getVideo();
if (!video) {
showMessage('Video not found');
return;
}
const playbackState = capturePlaybackState(video);
try {
if (getFullscreenElement()) {
await exitCurrentFullscreen();
schedulePlaybackRestore(video, playbackState);
return;
}
const container = findPlayerContainer(video, {
ignoreFullscreen: true
});
// Always fullscreen the container instead of the video itself
// so the tools can stay inside the fullscreen subtree.
if (container && container !== video) {
await requestElementFullscreen(container);
} else {
const fallback = getComposedParent(video);
if (fallback && fallback !== video) {
await requestElementFullscreen(fallback);
} else {
await requestElementFullscreen(video);
}
}
schedulePlaybackRestore(video, playbackState);
} catch (error) {
console.error('[VRZ V10.4] Fullscreen failed:', error);
showMessage('Fullscreen failed');
schedulePlaybackRestore(video, playbackState);
}
}
async function repairDirectVideoFullscreen() {
const fullscreenElement = getFullscreenElement();
if (
directFullscreenRepairing ||
!(fullscreenElement instanceof HTMLVideoElement)
) {
return;
}
const target = findPlayerContainer(fullscreenElement, {
ignoreFullscreen: true
});
if (!target || target === fullscreenElement) {
return;
}
directFullscreenRepairing = true;
try {
// Many browsers allow switching the fullscreen element
// without exiting first, preserving user activation more reliably.
await requestElementFullscreen(target);
} catch (error) {
console.warn('[VRZ V10.4] Could not redirect direct-video fullscreen:', error);
} finally {
directFullscreenRepairing = false;
}
}
// ========================================================
// DOWNLOAD - VIDEO SOURCES
// ========================================================
function getVideoSourceCandidates(video) {
if (!video) {
return [];
}
const candidates = [
video.currentSrc,
video.src,
video.getAttribute('src'),
video.getAttribute('data-src'),
video.getAttribute('data-video-src'),
video.getAttribute('data-video-url'),
video.getAttribute('data-video'),
video.getAttribute('data-url'),
video.getAttribute('data-stream'),
video.getAttribute('data-stream-url'),
video.getAttribute('data-playback-url'),
video.getAttribute('data-content-url'),
video.getAttribute('data-hls'),
video.getAttribute('data-m3u8'),
video.getAttribute('data-mpd'),
video.getAttribute('data-manifest'),
video.getAttribute('data-original'),
video.getAttribute('data-file')
];
for (const source of video.querySelectorAll('source')) {
candidates.push(
source.src,
source.getAttribute('src'),
source.getAttribute('data-src'),
source.getAttribute('data-video-src'),
source.getAttribute('data-stream-url'),
source.getAttribute('data-hls'),
source.getAttribute('data-m3u8'),
source.getAttribute('data-mpd'),
source.getAttribute('data-manifest'),
source.getAttribute('data-file')
);
}
return [
...new Set(
candidates
.filter(Boolean)
.map(value => safeUrl(value))
)
];
}
function getVideoMimeType(video) {
if (!video) {
return '';
}
const directType = video.getAttribute('type');
if (directType) {
return directType.toLowerCase();
}
const sources = [...video.querySelectorAll('source')];
const activeSource = sources.find(
source => source.src === video.currentSrc
);
return (
activeSource?.type ||
sources.find(source => source.type)?.type ||
''
).toLowerCase();
}
function detectDownloadPlatform() {
const host = location.hostname.toLowerCase();
if (
host === 'youtu.be' ||
host.endsWith('.youtube.com') ||
host === 'youtube.com' ||
host.endsWith('.youtube-nocookie.com')
) {
return 'youtube';
}
if (
host === 'instagram.com' ||
host.endsWith('.instagram.com')
) {
return 'instagram';
}
if (
host === 'facebook.com' ||
host.endsWith('.facebook.com') ||
host === 'fb.watch' ||
host.endsWith('.fb.watch')
) {
return 'facebook';
}
return '';
}
function getPlatformRequestHeaders(platform = detectDownloadPlatform()) {
if (platform === 'youtube') {
return {
Referer: 'https://www.youtube.com/'
};
}
if (platform === 'instagram') {
return {
Referer: 'https://www.instagram.com/'
};
}
if (platform === 'facebook') {
return {
Referer: 'https://www.facebook.com/'
};
}
return {
Referer: location.href
};
}
function getMetaContent(selectors) {
for (const selector of selectors) {
const element = document.querySelector(selector);
const value = element?.content || element?.href || '';
if (value) {
return value;
}
}
return '';
}
function getCanonicalVideoPageUrl(platform = detectDownloadPlatform()) {
if (platform === 'facebook') {
try {
const currentVideo = findFreshDownloadVideo('facebook') || getVideo();
const identity = getFacebookCurrentVideoIdentity(currentVideo);
if (identity?.id && identity?.url) {
return identity.url.split('#')[0];
}
} catch {
// Fall through to canonical/meta URL detection.
}
}
if (platform === 'youtube') {
let videoId = '';
try {
videoId = new URL(location.href).searchParams.get('v') || '';
} catch {
// Ignore.
}
if (!videoId) {
const match = location.pathname.match(
/\/(?:shorts|embed|live)\/([^/?#]+)/i
);
videoId = match?.[1] || '';
}
if (!videoId && location.hostname === 'youtu.be') {
videoId = location.pathname.split('/').filter(Boolean)[0] || '';
}
if (!videoId) {
try {
const player = document.getElementById('movie_player');
videoId = player?.getVideoData?.()?.video_id || '';
} catch {
// Ignore.
}
}
if (videoId) {
return `https://www.youtube.com/watch?v=${encodeURIComponent(videoId)}`;
}
}
const canonical = getMetaContent([
'link[rel="canonical"]',
'meta[property="og:url"]'
]);
if (canonical) {
return safeUrl(canonical);
}
return location.href.split('#')[0];
}
function getCookieValue(name) {
const prefix = `${name}=`;
for (const part of String(document.cookie || '').split(';')) {
const value = part.trim();
if (value.startsWith(prefix)) {
return decodeURIComponent(value.slice(prefix.length));
}
}
return '';
}
function extractInstagramIdentityFromUrl(value) {
if (!value) {
return null;
}
try {
const url = new URL(value, location.href);
const match = url.pathname.match(
/\/(?:p|reel|reels|tv)\/([A-Za-z0-9_-]{5,40})(?:\/|$)/i
);
if (!match) {
return null;
}
const shortcode = normalizeInstagramShortcode(match[1]);
if (!shortcode) {
return null;
}
return {
shortcode,
mediaId: instagramShortcodeToMediaId(shortcode),
url: `https://www.instagram.com/reel/${shortcode}/`
};
} catch {
return null;
}
}
function extractInstagramStoryIdentityFromUrl(value) {
if (!value) {
return null;
}
try {
const url = new URL(value, location.href);
const match = url.pathname.match(
/\/stories\/([^/?#]+)(?:\/(\d{5,40}))?(?:\/|$)/i
);
if (!match) {
return null;
}
const rawUser = String(match[1] || '');
const storyId = normalizeInstagramMediaId(match[2] || '');
const isHighlight = rawUser.toLowerCase() === 'highlights';
const username = isHighlight
? ''
: normalizeInstagramUsername(rawUser);
const highlightId = isHighlight ? storyId : '';
if (!storyId && !username) {
return null;
}
const context = getInstagramStoryContext({
username,
storyId,
highlightId
}) || {};
return {
shortcode: '',
mediaId: storyId,
storyId,
username,
userId: normalizeInstagramMediaId(context.userId),
highlightId,
surface: 'story',
url: url.href.split('#')[0]
};
} catch {
return null;
}
}
function getInstagramCurrentMediaIdentity(video) {
// V10.1 route lock:
// - Story URLs identify one exact Story ID and must never be
// overridden by stale network/cache identities.
// - /p/, /reel/, /reels/, /tv/ routes identify the opened media
// directly (this restores the V9.5 Reel path).
// - Home/Explore/Profile keep the V9.6 visible-card resolver below.
const routeStoryIdentity = extractInstagramStoryIdentityFromUrl(location.href);
if (routeStoryIdentity?.surface === 'story') {
return {
...routeStoryIdentity,
score: routeStoryIdentity.storyId ? 50000 : 49000,
source: routeStoryIdentity.storyId
? 'story-route-lock'
: 'story-route-first-open-lock'
};
}
const routeMediaIdentity = extractInstagramIdentityFromUrl(location.href);
if (routeMediaIdentity?.shortcode) {
return {
...routeMediaIdentity,
surface: 'media-route',
score: 45000,
source: 'media-route-lock'
};
}
const candidates = [];
const seen = new Set();
const videoRect = video?.getBoundingClientRect?.() || null;
const addIdentity = (identity, score = 0, source = '') => {
if (!identity) {
return;
}
const shortcode = normalizeInstagramShortcode(identity.shortcode);
const storyId = normalizeInstagramMediaId(identity.storyId);
const mediaId = normalizeInstagramMediaId(
identity.mediaId ||
storyId ||
(shortcode ? instagramShortcodeToMediaId(shortcode) : '')
);
const username = normalizeInstagramUsername(identity.username);
const userId = normalizeInstagramMediaId(identity.userId);
const highlightId = normalizeInstagramMediaId(identity.highlightId);
const surface = identity.surface || (storyId ? 'story' : '');
if (!shortcode && !mediaId && !username) {
return;
}
const url = (
identity.url ||
(shortcode ? `https://www.instagram.com/reel/${shortcode}/` : '')
);
const key = `${shortcode}|${mediaId}|${storyId}|${username}|${url}`;
if (seen.has(key)) {
return;
}
seen.add(key);
candidates.push({
shortcode,
mediaId,
storyId,
username,
userId,
highlightId,
surface,
url,
score,
source
});
};
const addUrl = (url, score = 0, source = '') => {
addIdentity(extractInstagramIdentityFromUrl(url), score, source);
};
const currentSrc = video?.currentSrc || video?.src || '';
const urlIndexKey = getInstagramUrlIndexKey(currentSrc);
const indexedIdentity = urlIndexKey
? instagramUrlIdentityStore.get(urlIndexKey)
: null;
if (indexedIdentity) {
addIdentity(indexedIdentity, 9000, 'active-source-index');
}
let element = video;
for (let depth = 0; element && depth < 16; depth += 1) {
if (
element === document.body ||
element === document.documentElement
) {
break;
}
const depthScore = Math.max(0, 5200 - depth * 180);
for (const attribute of [
'data-shortcode',
'data-media-code',
'data-code'
]) {
const shortcode = normalizeInstagramShortcode(
element.getAttribute?.(attribute) || ''
);
if (shortcode) {
addIdentity({ shortcode }, depthScore + 900, `attr-${attribute}`);
}
}
for (const attribute of [
'data-media-id',
'data-mediaid',
'data-id'
]) {
const mediaId = normalizeInstagramMediaId(
element.getAttribute?.(attribute) || ''
);
if (mediaId) {
addIdentity({ mediaId }, depthScore + 700, `attr-${attribute}`);
}
}
let anchors = [];
try {
anchors = [
...element.querySelectorAll(
'a[href*="/reel/"], a[href*="/reels/"], a[href*="/p/"], a[href*="/tv/"]'
)
].slice(0, 220);
} catch {
anchors = [];
}
for (const anchor of anchors) {
const href = anchor.href || anchor.getAttribute('href') || '';
let score = depthScore;
try {
const rect = anchor.getBoundingClientRect();
if (videoRect) {
const overlaps = !(
rect.right < videoRect.left ||
rect.left > videoRect.right ||
rect.bottom < videoRect.top ||
rect.top > videoRect.bottom
);
if (overlaps) {
score += 3600;
} else if (rect.width > 0 && rect.height > 0) {
const anchorCenterX = rect.left + rect.width / 2;
const anchorCenterY = rect.top + rect.height / 2;
const videoCenterX = videoRect.left + videoRect.width / 2;
const videoCenterY = videoRect.top + videoRect.height / 2;
const distance = Math.hypot(
anchorCenterX - videoCenterX,
anchorCenterY - videoCenterY
);
score += Math.max(0, 1800 - distance * 1.3);
}
}
} catch {
// Geometry is optional.
}
addUrl(href, score, `ancestor-anchor-${depth}`);
}
// Keep the V9.6 serialized-props fallback for feed cards that
// have no visible permalink around the <video> element.
if (depth < 8) {
let html = '';
try {
html = element.outerHTML || '';
} catch {
html = '';
}
if (html && html.length <= 3 * 1024 * 1024) {
for (const regex of [
/["'](?:shortcode|code)["']\s*:\s*["']([A-Za-z0-9_-]{5,40})["']/gi,
/(?:shortcode|code)\\?"\s*:\s*\\?"([A-Za-z0-9_-]{5,40})\\?"/gi
]) {
let match;
let count = 0;
while ((match = regex.exec(html)) && count < 12) {
addIdentity(
{ shortcode: match[1] },
depthScore + 500,
`serialized-${depth}`
);
count += 1;
}
}
}
}
element = element.parentElement;
}
// V9.6 geometry fallback for Home/Explore/profile overlays.
if (videoRect) {
let anchors = [];
try {
anchors = [
...document.querySelectorAll(
'a[href*="/reel/"], a[href*="/reels/"], a[href*="/p/"], a[href*="/tv/"]'
)
].slice(0, 1400);
} catch {
anchors = [];
}
const videoCenterX = videoRect.left + videoRect.width / 2;
const videoCenterY = videoRect.top + videoRect.height / 2;
for (const anchor of anchors) {
try {
const rect = anchor.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) {
continue;
}
const anchorCenterX = rect.left + rect.width / 2;
const anchorCenterY = rect.top + rect.height / 2;
const distance = Math.hypot(
anchorCenterX - videoCenterX,
anchorCenterY - videoCenterY
);
if (distance > Math.max(window.innerWidth, window.innerHeight) * 1.25) {
continue;
}
addUrl(
anchor.href || anchor.getAttribute('href') || '',
Math.max(0, 2600 - distance * 1.5),
'document-geometry'
);
} catch {
// Ignore individual anchor failures.
}
}
}
candidates.sort((a, b) => b.score - a.score);
return candidates[0] || {
shortcode: '',
mediaId: '',
storyId: '',
username: '',
userId: '',
highlightId: '',
surface: '',
url: '',
score: 0,
source: 'none'
};
}
function getInstagramShortcode(pageUrl = getCanonicalVideoPageUrl('instagram')) {
try {
const url = new URL(pageUrl, location.href);
const match = url.pathname.match(
/\/(?:p|reel|reels|tv)\/([^/?#]+)/i
);
return match?.[1] || '';
} catch {
return '';
}
}
function instagramShortcodeToMediaId(shortcode) {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
let value = 0n;
for (const character of String(shortcode || '')) {
const digit = alphabet.indexOf(character);
if (digit < 0) {
return '';
}
value = value * 64n + BigInt(digit);
}
return value > 0n ? value.toString() : '';
}
function collectInstagramProductMedia(item) {
const result = [];
const queue = [item];
const seen = new Set();
while (queue.length) {
const current = queue.shift();
if (!current || typeof current !== 'object' || seen.has(current)) {
continue;
}
seen.add(current);
if (
Array.isArray(current.video_versions) ||
current.video_url
) {
result.push(current);
}
if (Array.isArray(current.carousel_media)) {
queue.push(...current.carousel_media);
}
if (current.if_not_gated_logged_out) {
queue.push(current.if_not_gated_logged_out);
}
}
return result;
}
function buildInstagramApiHeaders(pageUrl) {
const headers = {
'X-IG-App-ID': '936619743392459',
'X-ASBD-ID': '359341',
'X-IG-WWW-Claim': '0',
'Accept': '*/*',
'Origin': 'https://www.instagram.com',
'Referer': pageUrl || 'https://www.instagram.com/'
};
const csrfToken = getCookieValue('csrftoken');
if (csrfToken) {
headers['X-CSRFToken'] = csrfToken;
}
return headers;
}
function addInstagramProductCandidates(result, seen, item, pageUrl) {
for (const media of collectInstagramProductMedia(item)) {
const hasAudio = media.has_audio;
const versions = Array.isArray(media.video_versions)
? media.video_versions
: [];
for (const version of versions) {
const url = version?.url;
if (!url) {
continue;
}
// Skip known byte-range fragment URLs. These can be valid
// media fragments but are not standalone playable files.
if (isPartialMediaUrl(url)) {
continue;
}
const width = Number(version?.width || 0);
const height = Number(version?.height || 0);
const bitrate = Number(version?.bitrate || 0);
const qualityScore = (
height * 1_000_000 +
width * 1_000 +
Math.min(999_999, Math.round(bitrate / 10))
);
addSourceRecord(result, seen, url, {
kind: 'direct',
source: 'instagram-api-video_versions',
contentType: 'video/mp4',
scoreBonus: 2_000_000 + qualityScore,
likelyMuxed: true,
headers: {
Referer: pageUrl || 'https://www.instagram.com/'
},
width,
height,
bitrate
});
}
if (media.video_url) {
addSourceRecord(result, seen, media.video_url, {
kind: 'direct',
source: 'instagram-api-video_url',
contentType: 'video/mp4',
scoreBonus: 80_000,
likelyMuxed: true,
headers: {
Referer: pageUrl || 'https://www.instagram.com/'
},
width: Number(media?.original_width || media?.dimensions?.width || 0),
height: Number(media?.original_height || media?.dimensions?.height || 0)
});
}
}
}
function getStoredInstagramCandidates(identity) {
const result = [];
const seen = new Set();
const cutoff = nowMs() - CONFIG.mediaUrlMaxAgeMs;
for (const key of instagramIdentityKeys(identity)) {
const records = instagramMediaSourceStore.get(key);
if (!records) {
continue;
}
for (const record of records.values()) {
if (Number(record.timestamp || 0) < cutoff) {
continue;
}
addSourceRecord(result, seen, record.url, {
...record,
scoreBonus: Number(record.scoreBonus || 0) + 260000,
headers: getPlatformRequestHeaders('instagram')
});
}
}
return result;
}
async function getInstagramApiCandidates(video, resolvedIdentity = null) {
const result = [];
const seen = new Set();
const identity = resolvedIdentity || getInstagramCurrentMediaIdentity(video);
const pageUrl = identity.url || getCanonicalVideoPageUrl('instagram');
const shortcode = (
identity.shortcode ||
getInstagramShortcode(pageUrl)
);
const mediaId = (
identity.mediaId ||
instagramShortcodeToMediaId(shortcode)
);
if (!mediaId) {
return result;
}
const endpoints = [
`https://i.instagram.com/api/v1/media/${mediaId}/info/`,
`https://www.instagram.com/api/v1/media/${mediaId}/info/`
];
let lastError = null;
for (const endpoint of endpoints) {
try {
const response = await gmRequest({
url: endpoint,
headers: buildInstagramApiHeaders(pageUrl),
responseType: 'json',
timeout: 20000
});
const payload = (
response.response && typeof response.response === 'object'
? response.response
: JSON.parse(response.responseText || '{}')
);
captureInstagramResponseObject(
payload,
'instagram-media-info-api'
);
const items = Array.isArray(payload?.items)
? payload.items
: [];
for (const item of items) {
addInstagramProductCandidates(
result,
seen,
item,
pageUrl
);
}
for (const item of getStoredInstagramCandidates(identity)) {
if (!seen.has(item.url)) {
seen.add(item.url);
result.push(item);
}
}
if (result.length) {
return result.sort((a, b) => {
const scoreA = Number(a.scoreBonus || 0);
const scoreB = Number(b.scoreBonus || 0);
return scoreB - scoreA;
});
}
} catch (error) {
lastError = error;
console.debug(
'[VRZ V10.4] Instagram media info endpoint failed:',
endpoint,
error
);
}
}
if (lastError) {
console.debug('[VRZ V10.4] Instagram API extraction unavailable:', lastError);
}
return result;
}
function findInstagramStoryUserIdInDocument(identity = {}) {
const context = getInstagramStoryContext(identity);
const contextUserId = normalizeInstagramMediaId(context?.userId);
if (contextUserId) {
return contextUserId;
}
const username = normalizeInstagramUsername(identity.username);
if (!username) {
return '';
}
const escapedUsername = username.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const usernameRegex = new RegExp(
`"username"\\s*:\\s*"${escapedUsername}"`,
'i'
);
for (const script of [...document.scripts].slice(0, 500)) {
const text = script.textContent || '';
if (!text || text.length > 12 * 1024 * 1024) {
continue;
}
const match = usernameRegex.exec(text);
if (!match) {
continue;
}
const start = Math.max(0, match.index - 6000);
const end = Math.min(text.length, match.index + 6000);
const windowText = text.slice(start, end);
const idMatch = windowText.match(
/"(?:pk|id)"\s*:\s*"?(\d{5,30})"?/i
);
const userId = normalizeInstagramMediaId(idMatch?.[1] || '');
if (userId) {
rememberInstagramStoryContext({
username,
userId,
storyId: identity.storyId,
highlightId: identity.highlightId
});
return userId;
}
}
return '';
}
function decodeInstagramBootstrapText(value) {
return String(value || '')
.replace(/"/gi, '"')
.replace(/"/gi, '"')
.replace(/&/gi, '&')
.replace(/</gi, '<')
.replace(/>/gi, '>')
.replace(/\\u0026/gi, '&')
.replace(/\\u003d/gi, '=')
.replace(/\\u002f/gi, '/')
.replace(/\\u003a/gi, ':');
}
function findInstagramStoryUserIdInText(text, identity = {}) {
const username = normalizeInstagramUsername(identity.username);
if (!username || !text) {
return '';
}
const forms = [
String(text),
decodeInstagramBootstrapText(text),
decodeInstagramBootstrapText(text).replace(/\\"/g, '"')
];
const escapedUsername = username.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const patterns = [
new RegExp(`"username"\\s*:\\s*"${escapedUsername}"`, 'ig'),
new RegExp(`\\\\"username\\\\"\\s*:\\s*\\\\"${escapedUsername}\\\\"`, 'ig')
];
for (const body of forms) {
for (const pattern of patterns) {
pattern.lastIndex = 0;
let match;
while ((match = pattern.exec(body))) {
const start = Math.max(0, match.index - 9000);
const end = Math.min(body.length, match.index + 9000);
const windowText = body.slice(start, end);
const candidates = [
...windowText.matchAll(/"(?:pk|id)"\s*:\s*"?(\d{5,30})"?/gi),
...windowText.matchAll(/\\"(?:pk|id)\\"\s*:\s*\\"?(\d{5,30})/gi)
];
for (const candidate of candidates) {
const userId = normalizeInstagramMediaId(candidate?.[1] || '');
if (userId) {
return userId;
}
}
}
}
}
return '';
}
function captureInstagramStoryBootstrapHtml(html, identity = {}) {
const body = String(html || '');
if (!body || body.length > 40 * 1024 * 1024) {
return '';
}
const scriptRegex = /<script\b[^>]*(?:type=["']application\/json["']|data-sjs)[^>]*>([\s\S]*?)<\/script>/gi;
let match;
let parsedCount = 0;
while ((match = scriptRegex.exec(body)) && parsedCount < 240) {
const raw = decodeInstagramBootstrapText(match[1] || '').trim();
if (!raw || raw.length > 20 * 1024 * 1024) {
continue;
}
try {
const parsed = JSON.parse(raw);
parsedCount += 1;
captureInstagramResponseObject(
parsed,
'instagram-story-page-bootstrap'
);
} catch {
// Instagram also emits non-JSON scripts; ignore them.
}
}
const contextUserId = normalizeInstagramMediaId(
getInstagramStoryContext(identity)?.userId
);
return (
contextUserId ||
findInstagramStoryUserIdInText(body, identity)
);
}
async function bootstrapInstagramStoryContext(identity = {}) {
const existing = (
normalizeInstagramMediaId(identity.userId) ||
normalizeInstagramMediaId(getInstagramStoryContext(identity)?.userId) ||
findInstagramStoryUserIdInDocument(identity)
);
if (existing) {
return existing;
}
const pageUrl = identity.url || location.href;
try {
const response = await gmRequest({
url: pageUrl,
headers: {
...buildInstagramApiHeaders(pageUrl),
Accept: 'text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8'
},
responseType: 'text',
timeout: 12000
});
const html = response.responseText || String(response.response || '');
const userId = normalizeInstagramMediaId(
captureInstagramStoryBootstrapHtml(html, identity)
);
if (userId) {
rememberInstagramStoryContext({
username: identity.username,
userId,
storyId: identity.storyId || identity.mediaId,
highlightId: identity.highlightId
});
return userId;
}
} catch (error) {
console.debug('[VRZ V10.4] Instagram Story page bootstrap failed:', error);
}
return normalizeInstagramMediaId(
getInstagramStoryContext(identity)?.userId
);
}
async function resolveInstagramStoryUserId(identity = {}) {
const direct = (
normalizeInstagramMediaId(identity.userId) ||
normalizeInstagramMediaId(getInstagramStoryContext(identity)?.userId)
);
if (direct) {
return direct;
}
const documentUserId = findInstagramStoryUserIdInDocument(identity);
if (documentUserId) {
return documentUserId;
}
const username = normalizeInstagramUsername(identity.username);
const profileLookup = async () => {
if (!username) {
return '';
}
try {
const endpoint = (
'https://www.instagram.com/api/v1/users/web_profile_info/' +
`?username=${encodeURIComponent(username)}`
);
const response = await gmRequest({
url: endpoint,
headers: {
...buildInstagramApiHeaders(identity.url || location.href),
'X-Requested-With': 'XMLHttpRequest'
},
responseType: 'json',
timeout: 10000
});
const payload = (
response.response && typeof response.response === 'object'
? response.response
: JSON.parse(response.responseText || '{}')
);
const userId = normalizeInstagramMediaId(
payload?.data?.user?.id ||
payload?.data?.user?.pk ||
payload?.user?.id ||
payload?.user?.pk ||
''
);
if (userId) {
rememberInstagramStoryContext({
username,
userId,
storyId: identity.storyId,
highlightId: identity.highlightId
});
}
return userId;
} catch (error) {
console.debug('[VRZ V10.4] Instagram Story profile lookup failed:', error);
return '';
}
};
// First-open Story fix: run both independent identity resolvers at
// the same time. Whichever learns the user ID first unlocks
// reels_media immediately; the other request may still warm caches.
const bootstrapPromise = bootstrapInstagramStoryContext(identity)
.catch(error => {
console.debug('[VRZ V10.4] Instagram Story bootstrap failed:', error);
return '';
});
const profilePromise = profileLookup();
try {
const userId = await Promise.any([
bootstrapPromise.then(value => {
if (!value) throw new Error('Story bootstrap returned no user ID');
return value;
}),
profilePromise.then(value => {
if (!value) throw new Error('Story profile lookup returned no user ID');
return value;
})
]);
if (userId) {
return normalizeInstagramMediaId(userId);
}
} catch {
// Both first-open resolvers failed; use any context captured
// while they were running before giving up.
}
return (
findInstagramStoryUserIdInDocument(identity) ||
normalizeInstagramMediaId(getInstagramStoryContext(identity)?.userId)
);
}
async function getInstagramStoryApiCandidates(video, resolvedIdentity = null) {
const routeIdentity = extractInstagramStoryIdentityFromUrl(location.href);
const resolved = resolvedIdentity || getInstagramCurrentMediaIdentity(video) || {};
const identity = routeIdentity?.surface === 'story'
? {
...resolved,
...routeIdentity,
storyId: normalizeInstagramMediaId(
routeIdentity.storyId || resolved.storyId || resolved.mediaId
),
mediaId: normalizeInstagramMediaId(
routeIdentity.storyId || resolved.storyId || resolved.mediaId
),
userId: normalizeInstagramMediaId(
routeIdentity.userId || resolved.userId
),
surface: 'story'
}
: resolved;
if (identity.surface !== 'story') {
return [];
}
const result = [];
const seen = new Set();
const activeVideoKey = getInstagramUrlIndexKey(
video?.currentSrc || video?.src || ''
);
const activeIndexedIdentity = activeVideoKey
? instagramUrlIdentityStore.get(activeVideoKey)
: null;
const storyId = normalizeInstagramMediaId(
identity.storyId ||
identity.mediaId ||
activeIndexedIdentity?.storyId ||
activeIndexedIdentity?.mediaId
);
const highlightId = normalizeInstagramMediaId(identity.highlightId);
const userId = await resolveInstagramStoryUserId(identity);
const reelKey = highlightId
? `highlight:${highlightId}`
: userId;
if (!reelKey) {
return result;
}
const endpoint = (
'https://i.instagram.com/api/v1/feed/reels_media/' +
`?reel_ids=${encodeURIComponent(reelKey)}`
);
try {
const response = await gmRequest({
url: endpoint,
headers: buildInstagramApiHeaders(identity.url || location.href),
responseType: 'json',
timeout: 22000
});
const payload = (
response.response && typeof response.response === 'object'
? response.response
: JSON.parse(response.responseText || '{}')
);
captureInstagramResponseObject(
payload,
'instagram-story-reels-media-api'
);
const reels = (
payload?.reels && typeof payload.reels === 'object'
? payload.reels
: {}
);
const expectedUsername = normalizeInstagramUsername(identity.username);
for (const reel of Object.values(reels)) {
const reelUsername = normalizeInstagramUsername(
reel?.user?.username || ''
);
if (
expectedUsername &&
reelUsername &&
reelUsername !== expectedUsername
) {
continue;
}
for (const item of Array.isArray(reel?.items) ? reel.items : []) {
const itemStoryId = normalizeInstagramMediaId(
item?.pk || item?.id || item?.media_id || ''
);
// If Instagram already exposed the numeric Story ID, lock to it.
if (storyId && itemStoryId !== storyId) {
continue;
}
// On the FIRST Story open Instagram may briefly keep the route at
// /stories/username/ without the numeric ID. In that state never
// accept every Story from the user. Resolve the exact visible item
// by matching its video_versions path to the current <video> source.
const itemCandidates = [];
const itemSeen = new Set();
addInstagramProductCandidates(
itemCandidates,
itemSeen,
item,
identity.url || location.href
);
if (!storyId) {
if (!activeVideoKey) {
continue;
}
const activeItemMatch = itemCandidates.some(candidate => (
getInstagramUrlIndexKey(candidate.url) === activeVideoKey
));
if (!activeItemMatch) {
continue;
}
}
for (const candidate of itemCandidates) {
if (seen.has(candidate.url)) {
continue;
}
seen.add(candidate.url);
result.push({
...candidate,
source: `instagram-story-exact-${candidate.source || 'video_versions'}`,
scoreBonus: Number(candidate.scoreBonus || 0) + 2_000_000,
storyId: itemStoryId
});
}
}
}
// Only reuse raw cache records tied to the exact current
// Story ID. Read the store directly so the identity metadata is
// preserved; generic source conversion intentionally drops it.
if (storyId) {
for (const key of [`story:${storyId}`, `id:${storyId}`]) {
const records = instagramMediaSourceStore.get(key);
if (!records) {
continue;
}
for (const record of records.values()) {
const recordStoryId = normalizeInstagramMediaId(
record?.identity?.storyId ||
record?.identity?.mediaId ||
''
);
if (
recordStoryId !== storyId ||
seen.has(record.url) ||
isInstagramFragmentUrl(record.url)
) {
continue;
}
seen.add(record.url);
result.push({
...record,
source: `instagram-story-exact-cache-${record.source || 'video'}`,
scoreBonus: Number(record.scoreBonus || 0) + 1_500_000,
headers: getPlatformRequestHeaders('instagram'),
likelyMuxed: true
});
}
}
}
} catch (error) {
console.debug(
'[VRZ V10.4] Instagram Story reels_media lookup failed:',
error
);
}
return result.sort((a, b) => (
(Number(b.height || 0) - Number(a.height || 0)) ||
(Number(b.width || 0) - Number(a.width || 0)) ||
(Number(b.bitrate || 0) - Number(a.bitrate || 0)) ||
(Number(b.scoreBonus || 0) - Number(a.scoreBonus || 0))
));
}
function getRecentInstagramStoryCandidates(video, identity = {}) {
if (identity.surface !== 'story') {
return [];
}
scanPerformanceMediaUrls();
const result = [];
const seen = new Set();
const now = nowMs();
const currentKey = getInstagramUrlIndexKey(
video?.currentSrc || video?.src || ''
);
for (const item of mediaUrlStore.values()) {
const age = now - Number(item.timestamp || 0);
if (age < 0 || age > 18000) {
continue;
}
if (!['direct', 'hls'].includes(item.kind)) {
continue;
}
let host = '';
try {
host = new URL(item.url).hostname.toLowerCase();
} catch {
continue;
}
if (
!host.includes('instagram') &&
!host.includes('cdninstagram') &&
!host.includes('fbcdn')
) {
continue;
}
const itemKey = getInstagramUrlIndexKey(item.url);
const activeMatch = Boolean(currentKey && itemKey === currentKey);
addSourceRecord(result, seen, item.url, {
...item,
source: activeMatch
? 'instagram-story-active-network-match'
: 'instagram-story-recent-network',
scoreBonus: (
(activeMatch ? 1_500_000 : 120000) +
Math.max(0, 18000 - age) * 8
),
likelyMuxed: item.kind === 'direct',
headers: getPlatformRequestHeaders('instagram'),
width: Number(video?.videoWidth || 0),
height: Number(video?.videoHeight || 0)
});
}
return result;
}
function instagramIdentitiesOverlap(a = {}, b = {}) {
const aKeys = new Set(instagramIdentityKeys(a));
const bKeys = instagramIdentityKeys(b);
return bKeys.some(key => aKeys.has(key));
}
function getRecentInstagramActiveCandidates(video, identity = {}) {
scanPerformanceMediaUrls();
const result = [];
const seen = new Set();
const now = nowMs();
const currentKey = getInstagramUrlIndexKey(
video?.currentSrc || video?.src || ''
);
const isStory = identity.surface === 'story';
const maxAge = isStory ? 90_000 : 150_000;
for (const item of mediaUrlStore.values()) {
const age = now - Number(item.timestamp || 0);
if (age < 0 || age > maxAge) {
continue;
}
if (!['direct', 'hls'].includes(item.kind)) {
continue;
}
let host = '';
try {
host = new URL(item.url).hostname.toLowerCase();
} catch {
continue;
}
if (
!host.includes('instagram') &&
!host.includes('cdninstagram') &&
!host.includes('fbcdn')
) {
continue;
}
const itemKey = getInstagramUrlIndexKey(item.url);
const indexedIdentity = itemKey
? instagramUrlIdentityStore.get(itemKey)
: null;
const activeMatch = Boolean(currentKey && itemKey === currentKey);
const identityMatch = Boolean(
indexedIdentity && instagramIdentitiesOverlap(identity, indexedIdentity)
);
// On generic Home/Profile/Explore surfaces the URL may not have
// a post shortcode. Recent CDN media is still valid, but do not
// let an old story/reel win unless it matches the active element
// or a captured identity.
if (!activeMatch && !identityMatch && age > (isStory ? 30_000 : 75_000)) {
continue;
}
addSourceRecord(result, seen, item.url, {
...item,
source: activeMatch
? 'instagram-active-network-match'
: identityMatch
? 'instagram-identity-network-match'
: 'instagram-recent-network-visible',
scoreBonus: (
(activeMatch ? 2_200_000 : 0) +
(identityMatch ? 1_400_000 : 0) +
(isStory ? 350_000 : 120_000) +
Math.max(0, maxAge - age) * 7
),
likelyMuxed: item.kind === 'direct',
headers: getPlatformRequestHeaders('instagram'),
width: Number(video?.videoWidth || 0),
height: Number(video?.videoHeight || 0)
});
}
return result;
}
function getInstagramScopedDomVideoCandidates(video, identity = {}) {
const result = [];
const seen = new Set();
const snippets = [];
let element = video;
for (let depth = 0; element && depth < 9; depth += 1) {
try {
const html = element.outerHTML || '';
if (html && html.length < 5 * 1024 * 1024) {
snippets.push({ text: html, score: 800_000 - depth * 40_000 });
}
} catch {
// Ignore inaccessible nodes.
}
if (
element === document.body ||
element === document.documentElement
) {
break;
}
element = element.parentElement;
}
// Post modals often keep the active media URL inside nearby script
// data rather than on the video element. Scan only a limited number
// of scripts to avoid making the page heavy.
for (const script of [...document.scripts].slice(-180)) {
const text = script.textContent || '';
if (
!text ||
text.length > 8 * 1024 * 1024 ||
!/(?:video_versions|video_url|cdninstagram|fbcdn|\.mp4)/i.test(text)
) {
continue;
}
snippets.push({ text, score: 260_000 });
}
const regexes = [
/https?:\?\/\?\/[^"'<>\\s]+(?:cdninstagram|instagram|fbcdn)[^"'<>\\s]+?\.mp4[^"'<>\\s]*/gi,
/https?%3A%2F%2F[^"'<>\\s]+(?:cdninstagram|instagram|fbcdn)[^"'<>\\s]+?\.mp4[^"'<>\\s]*/gi,
/(?:video_url|playback_url|url)\?"\s*:\s*\?"((?:https?:\?\/\?\/|https?%3A%2F%2F)[^"<>]+?)\?"/gi
];
for (const snippet of snippets) {
for (const regex of regexes) {
let match;
let count = 0;
while ((match = regex.exec(snippet.text)) && count < 60) {
count += 1;
const raw = match[1] || match[0];
const url = decodeEscapedMediaUrl(raw);
if (!url || isImageLikeUrl(url) || isAudioLikeUrl(url)) {
continue;
}
if (!/\.(?:mp4|m4v)(?:$|[?#])/i.test(url)) {
continue;
}
const urlKey = getInstagramUrlIndexKey(url);
const indexedIdentity = urlKey
? instagramUrlIdentityStore.get(urlKey)
: null;
const identityMatch = Boolean(
indexedIdentity && instagramIdentitiesOverlap(identity, indexedIdentity)
);
addSourceRecord(result, seen, url, {
kind: 'direct',
source: identityMatch
? 'instagram-active-dom-identity-video'
: 'instagram-active-dom-video-url',
contentType: 'video/mp4',
scoreBonus: snippet.score + (identityMatch ? 850_000 : 0),
likelyMuxed: true,
headers: getPlatformRequestHeaders('instagram'),
width: Number(video?.videoWidth || 0),
height: Number(video?.videoHeight || 0)
});
}
}
}
return result;
}
function getSafeInstagramVisibleFallbackCandidates(video, identity = {}) {
const result = [];
const seen = new Set();
const identityKeys = instagramIdentityKeys(identity);
const noIdentity = identityKeys.length === 0;
const now = nowMs();
const addSafe = item => {
if (
!item?.url ||
seen.has(item.url) ||
item.kind !== 'direct' ||
isInstagramFragmentUrl(item.url)
) {
return;
}
seen.add(item.url);
result.push(item);
};
for (const item of getRecentInstagramActiveCandidates(video, identity)) {
const name = String(item.source || '');
const age = now - Number(item.timestamp || 0);
const exactMatch = /active-network-match|identity-network-match/i.test(name);
const veryRecentPlayingFeed = Boolean(
noIdentity &&
video &&
!video.paused &&
age >= 0 &&
age <= 12000 &&
/recent-network-visible/i.test(name)
);
if (exactMatch || veryRecentPlayingFeed) {
addSafe({
...item,
source: exactMatch
? item.source
: 'instagram-feed-visible-progressive',
scoreBonus: Number(item.scoreBonus || 0) +
(exactMatch ? 1_000_000 : 250000)
});
}
}
// Nearby serialized DOM/script URLs are accepted only when tied to
// the current identity, or when no identity exists at all on Home.
for (const item of getInstagramScopedDomVideoCandidates(video, identity)) {
const name = String(item.source || '');
if (
/active-dom-identity-video/i.test(name) ||
(noIdentity && /active-dom-video-url/i.test(name))
) {
addSafe(item);
}
}
return result.sort((a, b) => (
(Number(b.scoreBonus || 0) - Number(a.scoreBonus || 0)) ||
(Number(b.height || 0) - Number(a.height || 0)) ||
(Number(b.width || 0) - Number(a.width || 0))
));
}
function isInstagramFragmentUrl(url) {
let source = String(url || '');
for (let index = 0; index < 2; index += 1) {
try {
const decoded = decodeURIComponent(source);
if (decoded === source) {
break;
}
source = decoded;
} catch {
break;
}
}
return Boolean(
/[?&](?:bytestart|byteend|range|start_byte|end_byte)=/i.test(source) ||
/\.(?:m4s|cmfv|cmfa)(?:$|[?#])/i.test(source)
);
}
async function probeInstagramCandidateV101(source) {
const url = source?.url || '';
const headers = {
...getPlatformRequestHeaders('instagram'),
...(source?.headers || {})
};
if (
!url ||
isImageLikeUrl(url) ||
isInstagramFragmentUrl(url)
) {
return {
valid: false,
reason: 'image or byte-range fragment'
};
}
let contentType = String(source?.contentType || '')
.split(';')[0]
.trim()
.toLowerCase();
let finalUrl = url;
try {
const head = await gmRequest({
method: 'HEAD',
url,
headers,
responseType: 'text',
timeout: 15000
});
finalUrl = head.finalUrl || head.responseURL || finalUrl;
contentType = (
getResponseHeader(head.responseHeaders, 'content-type') ||
contentType
).split(';')[0].trim().toLowerCase();
if (
isImageLikeUrl(finalUrl) ||
isRejectedDownloadMime(contentType) ||
isInstagramFragmentUrl(finalUrl)
) {
return {
valid: false,
reason: `rejected Instagram source ${contentType || 'unknown'}`
};
}
if (
contentType.includes('mpegurl') ||
contentType.includes('vnd.apple.mpegurl')
) {
return {
valid: true,
kind: 'hls',
contentType,
finalUrl
};
}
if (contentType.includes('dash+xml')) {
return {
valid: true,
kind: 'dash',
contentType,
finalUrl
};
}
// This is the V9.5/V9.6 behavior that successfully downloaded
// normal Instagram posts and Reels: a complete HTTP video source
// is downloaded directly instead of being copied into a new Blob.
if (contentType.startsWith('video/')) {
return {
valid: true,
kind: 'direct',
contentType,
extension: getExtensionFromMimeType(contentType),
finalUrl
};
}
} catch (error) {
console.debug('[VRZ V10.4] Instagram HEAD probe failed:', url, error);
}
try {
const probe = await gmRequest({
url,
headers: {
...headers,
Range: 'bytes=0-65535'
},
responseType: 'arraybuffer',
timeout: 20000
});
finalUrl = probe.finalUrl || probe.responseURL || finalUrl;
contentType = (
getResponseHeader(probe.responseHeaders, 'content-type') ||
contentType
).split(';')[0].trim().toLowerCase();
if (
isImageLikeUrl(finalUrl) ||
isRejectedDownloadMime(contentType) ||
isInstagramFragmentUrl(finalUrl)
) {
return {
valid: false,
reason: 'rejected Instagram probe response'
};
}
const detected = detectBinaryMedia(probe.response);
if (detected.kind === 'hls') {
return {
valid: true,
kind: 'hls',
contentType: contentType || 'application/vnd.apple.mpegurl',
finalUrl
};
}
if (
detected.kind === 'video' ||
contentType.startsWith('video/')
) {
return {
valid: true,
kind: 'direct',
contentType: contentType || 'video/mp4',
extension: detected.extension || getExtensionFromMimeType(contentType),
finalUrl
};
}
} catch (error) {
console.debug('[VRZ V10.4] Instagram range probe failed:', url, error);
}
return {
valid: false,
reason: 'not a verified Instagram video source'
};
}
function getInstagramSourceTrust(source = {}) {
const name = String(source.source || '').toLowerCase();
if (!source.url || isPartialMediaUrl(source.url)) {
return -100;
}
if (/video_versions/.test(name)) {
return 700;
}
if (
/instagram-(?:api-video_url|media-info-api|story-reels-media-api)/.test(name) ||
/reels-media-api/.test(name)
) {
return 620;
}
if (/instagram-active-progressive/.test(name)) {
return 420;
}
if (/instagram-(?:active-network-match|identity-network-match)/.test(name)) {
return 320;
}
if (/instagram-active-dom-identity-video/.test(name)) {
return 260;
}
if (/instagram-(?:recent-network|active-dom)/.test(name)) {
return 180;
}
return 100;
}
function compareInstagramDownloadSources(a, b, identity = {}) {
const trustDifference = (
getInstagramSourceTrust(b) - getInstagramSourceTrust(a)
);
if (trustDifference) {
return trustDifference;
}
const storyMode = identity.surface === 'story';
const aHeight = Number(a.height || 0);
const bHeight = Number(b.height || 0);
// Stories should always download the maximum source quality. On
// other Instagram surfaces, an explicit Resolution selection is
// respected; AUTO still means maximum quality.
if (!storyMode && preferredResolutionHeight > 0) {
const aDistance = aHeight > 0
? Math.abs(aHeight - preferredResolutionHeight)
: Number.MAX_SAFE_INTEGER;
const bDistance = bHeight > 0
? Math.abs(bHeight - preferredResolutionHeight)
: Number.MAX_SAFE_INTEGER;
if (aDistance !== bDistance) {
return aDistance - bDistance;
}
}
return (
(bHeight - aHeight) ||
(Number(b.width || 0) - Number(a.width || 0)) ||
(Number(b.bitrate || 0) - Number(a.bitrate || 0)) ||
(Number(b.scoreBonus || 0) - Number(a.scoreBonus || 0))
);
}
function mergeInstagramSource(existing, incoming) {
if (!existing) {
return { ...incoming };
}
const preferred = (
getInstagramSourceTrust(incoming) > getInstagramSourceTrust(existing)
? incoming
: existing
);
return {
...existing,
...preferred,
likelyMuxed: Boolean(existing.likelyMuxed || incoming.likelyMuxed),
width: Math.max(Number(existing.width || 0), Number(incoming.width || 0)),
height: Math.max(Number(existing.height || 0), Number(incoming.height || 0)),
bitrate: Math.max(Number(existing.bitrate || 0), Number(incoming.bitrate || 0)),
scoreBonus: Math.max(
Number(existing.scoreBonus || 0),
Number(incoming.scoreBonus || 0)
),
headers: {
...(existing.headers || {}),
...(incoming.headers || {})
}
};
}
async function getTrustedInstagramDownloadSources(video, options = {}) {
const identity = (
options.identity ||
getInstagramCurrentMediaIdentity(video)
);
const result = [];
const seen = new Set();
const addAll = items => {
for (const item of items || []) {
if (
!item?.url ||
seen.has(item.url) ||
isInstagramFragmentUrl(item.url) ||
item.kind === 'segment'
) {
continue;
}
seen.add(item.url);
result.push({ ...item });
}
};
showMessage('Checking Instagram sources...', 5000);
if (identity.surface === 'story') {
// V10.4 first-open Story path:
// 1) lock the currently visible <video> source immediately;
// 2) briefly allow reels_media/video_versions to enrich it with a
// higher-resolution variant;
// 3) never wait for a slow API call before a valid visible source can
// be verified and saved.
const visibleStorySources = [];
const visibleStorySeen = new Set();
if (
video?.currentSrc &&
/^https?:/i.test(video.currentSrc) &&
!isInstagramFragmentUrl(video.currentSrc)
) {
addSourceRecord(visibleStorySources, visibleStorySeen, video.currentSrc, {
kind: 'direct',
source: 'instagram-story-current-visible-video',
contentType: getVideoMimeType(video),
scoreBonus: 1_850_000,
likelyMuxed: true,
headers: getPlatformRequestHeaders('instagram'),
width: Number(video.videoWidth || 0),
height: Number(video.videoHeight || 0)
});
}
addAll(visibleStorySources);
addAll(getStoredInstagramCandidates(identity).filter(item => (
item.kind === 'direct' &&
item.likelyMuxed !== false &&
!isInstagramFragmentUrl(item.url)
)));
if (options.useApi !== false) {
const apiPromise = getInstagramStoryApiCandidates(video, identity);
if (visibleStorySources.length) {
// Give the API a short chance to return maximum-resolution
// video_versions. The unresolved request continues warming the
// captured cache even if the visible source wins this click.
const fastApiItems = await Promise.race([
apiPromise,
delay(900).then(() => [])
]);
addAll(fastApiItems);
} else {
addAll(await apiPromise);
}
}
return result.sort((a, b) => (
(Number(b.height || 0) - Number(a.height || 0)) ||
(Number(b.width || 0) - Number(a.width || 0)) ||
(Number(b.bitrate || 0) - Number(a.bitrate || 0)) ||
(Number(b.scoreBonus || 0) - Number(a.scoreBonus || 0))
));
}
// Proven V9.6 path: captured JSON tied to the visible card first.
addAll(getStoredInstagramCandidates(identity));
// Proven V9.5 route path: media/{id}/info returns video_versions for
// opened Reels/posts. Route locking above ensures the API is called
// for the current item, not a stale feed card.
if (options.useApi !== false) {
try {
addAll(await getInstagramApiCandidates(video, identity));
} catch (error) {
console.debug('[VRZ V10.4] Instagram API candidate lookup failed:', error);
}
}
// API calls can populate the captured-source store.
addAll(getStoredInstagramCandidates(identity));
const isSpecificRoute = Boolean(
extractInstagramIdentityFromUrl(location.href)
);
// V9.5 secondary path for opened posts/Reels.
if (isSpecificRoute) {
addAll(getMetaMediaCandidates('instagram').filter(
item => item.kind === 'direct' && item.likelyMuxed
));
addAll(getJsonLdMediaCandidates('instagram').filter(
item => item.kind === 'direct' && item.likelyMuxed
));
}
// Proven V9.6 fallback for Home/Explore/Profile/overlay video.
if (
video?.currentSrc &&
/^https?:/i.test(video.currentSrc) &&
!isInstagramFragmentUrl(video.currentSrc)
) {
addSourceRecord(result, seen, video.currentSrc, {
kind: 'direct',
source: 'instagram-active-progressive',
contentType: getVideoMimeType(video),
scoreBonus: 65000,
likelyMuxed: true,
headers: getPlatformRequestHeaders('instagram'),
width: Number(video.videoWidth || 0),
height: Number(video.videoHeight || 0)
});
}
if (!isSpecificRoute) {
addAll(getSafeInstagramVisibleFallbackCandidates(video, identity));
}
const activeUrlKey = getInstagramUrlIndexKey(
video?.currentSrc || video?.src || ''
);
if (activeUrlKey) {
for (const item of result) {
if (getInstagramUrlIndexKey(item.url) === activeUrlKey) {
item.scoreBonus = Number(item.scoreBonus || 0) + 1_000_000;
item.source = `${item.source || 'instagram'}-active-match`;
}
}
}
result.sort((a, b) => {
const aName = String(a.source || '');
const bName = String(b.source || '');
const aApi = /video_versions|api-video_url|media-info-api/i.test(aName) ? 1 : 0;
const bApi = /video_versions|api-video_url|media-info-api/i.test(bName) ? 1 : 0;
if (aApi !== bApi) {
return bApi - aApi;
}
if (preferredResolutionHeight > 0) {
const aHeight = Number(a.height || 0);
const bHeight = Number(b.height || 0);
const aDistance = aHeight > 0
? Math.abs(aHeight - preferredResolutionHeight)
: Number.MAX_SAFE_INTEGER;
const bDistance = bHeight > 0
? Math.abs(bHeight - preferredResolutionHeight)
: Number.MAX_SAFE_INTEGER;
if (aDistance !== bDistance) {
return aDistance - bDistance;
}
}
return (
(Number(b.scoreBonus || 0) - Number(a.scoreBonus || 0)) ||
(Number(b.height || 0) - Number(a.height || 0)) ||
(Number(b.width || 0) - Number(a.width || 0)) ||
(Number(b.bitrate || 0) - Number(a.bitrate || 0))
);
});
console.log('[VRZ V10.4] Instagram active media:', identity);
console.log('[VRZ V10.4] Instagram ranked sources:', result);
return result;
}
function buildGrabShortsFallbackUrl(platform, pageUrl) {
if (platform !== 'youtube' || !pageUrl) {
return '';
}
return (
'https://www.grabshorts.com/id/yt' +
`?s=22&url=${encodeURIComponent(pageUrl)}`
);
}
function openExternalDownloadFallback(platform, reason = '') {
// V9.8 keeps external fallback only for YouTube, where many formats
// are intentionally exposed as separate audio/video tracks. Instagram
// and Facebook now stay entirely on the internal active-media path.
if (platform !== 'youtube') {
return false;
}
const pageUrl = getCanonicalVideoPageUrl(platform);
const fallbackUrl = buildGrabShortsFallbackUrl(platform, pageUrl);
if (!fallbackUrl) {
return false;
}
console.warn(
'[VRZ V10.4] Opening YouTube fallback:',
reason,
fallbackUrl
);
showMessage('Opening YouTube fallback downloader...', 5000);
try {
if (typeof GM_openInTab === 'function') {
GM_openInTab(fallbackUrl, {
active: true,
insert: true,
setParent: true
});
return true;
}
} catch (error) {
console.warn('[VRZ V10.4] GM_openInTab failed:', error);
}
try {
const opened = window.open(
fallbackUrl,
'_blank',
'noopener,noreferrer'
);
return Boolean(opened);
} catch {
return false;
}
}
function decodeEscapedMediaUrl(value) {
let text = String(value || '').trim();
if (!text) {
return '';
}
text = text
.replace(/\\u0026/gi, '&')
.replace(/\\u003d/gi, '=')
.replace(/\\u002f/gi, '/')
.replace(/\\u003a/gi, ':')
.replace(/\\u003f/gi, '?')
.replace(/\\x26/gi, '&')
.replace(/\\\//g, '/')
.replace(/&/gi, '&');
try {
if (/^https?%3a/i.test(text)) {
text = decodeURIComponent(text);
}
} catch {
// Keep the partially decoded value.
}
if (!/^https?:\/\//i.test(text) && !/^blob:/i.test(text)) {
return '';
}
return safeUrl(text);
}
function addSourceRecord(target, seen, value, details = {}) {
const url = decodeEscapedMediaUrl(value) || safeUrl(value);
if (
!url ||
url.startsWith('data:') ||
isImageLikeUrl(url) ||
(String(details.source || '').includes('instagram') && isInstagramFragmentUrl(url)) ||
seen.has(url)
) {
return;
}
const kind = details.kind || classifyMediaUrl(
url,
details.contentType || '',
details.initiatorType || ''
);
if (
kind === 'unknown' ||
kind === 'audio' ||
kind === 'segment'
) {
return;
}
seen.add(url);
target.push({
url,
kind,
source: details.source || 'platform-page',
contentType: details.contentType || '',
scoreBonus: Number(details.scoreBonus || 0),
likelyMuxed: Boolean(details.likelyMuxed),
headers: {
...(details.headers || {})
},
width: Number(details.width || 0),
height: Number(details.height || 0),
bitrate: Number(details.bitrate || 0)
});
}
function getMetaMediaCandidates(platform) {
const result = [];
const seen = new Set();
const contentType = getMetaContent([
'meta[property="og:video:type"]',
'meta[name="twitter:player:stream:content_type"]'
]);
const selectors = [
'meta[property="og:video"]',
'meta[property="og:video:url"]',
'meta[property="og:video:secure_url"]',
'meta[name="twitter:player:stream"]',
'meta[itemprop="contentUrl"]',
'link[itemprop="contentUrl"]'
];
for (const selector of selectors) {
const nodes = document.querySelectorAll(selector);
for (const node of nodes) {
addSourceRecord(
result,
seen,
node.content || node.href || '',
{
source: 'page-meta',
contentType,
scoreBonus: 18_000,
likelyMuxed: true,
headers: getPlatformRequestHeaders(platform)
}
);
}
}
return result;
}
function parseMaybeJson(value) {
if (!value) {
return null;
}
if (typeof value === 'object') {
return value;
}
try {
return JSON.parse(String(value));
} catch {
return null;
}
}
function walkJsonMediaSources(value, callback, depth = 0) {
if (
value === null ||
value === undefined ||
depth > 12
) {
return;
}
if (Array.isArray(value)) {
for (const item of value.slice(0, 1000)) {
walkJsonMediaSources(item, callback, depth + 1);
}
return;
}
if (typeof value !== 'object') {
return;
}
for (const [key, item] of Object.entries(value)) {
const normalizedKey = key.toLowerCase();
if (
typeof item === 'string' &&
[
'contenturl',
'videourl',
'video_url',
'playable_url',
'playable_url_quality_hd',
'playable_url_dash',
'browser_native_hd_url',
'browser_native_sd_url',
'progressive_url',
'hls_playlist_url',
'manifest_url'
].includes(normalizedKey)
) {
callback(item, normalizedKey);
}
if (
normalizedKey === 'video_versions' &&
Array.isArray(item)
) {
for (const version of item) {
if (version?.url) {
callback(version.url, 'video_versions');
}
}
}
walkJsonMediaSources(item, callback, depth + 1);
}
}
function getJsonLdMediaCandidates(platform) {
const result = [];
const seen = new Set();
for (const script of document.querySelectorAll(
'script[type="application/ld+json"]'
)) {
const parsed = parseMaybeJson(script.textContent);
if (!parsed) {
continue;
}
walkJsonMediaSources(parsed, (url, key) => {
addSourceRecord(result, seen, url, {
source: `json-ld-${key}`,
scoreBonus: 16_000,
likelyMuxed: true,
headers: getPlatformRequestHeaders(platform)
});
});
}
return result;
}
function extractKeyedUrlsFromText(text, keys, callback) {
for (const key of keys) {
const regex = new RegExp(
`["']${key}["']\\s*:\\s*["']((?:\\\\.|[^"'\\\\])*)["']`,
'gi'
);
let match;
while ((match = regex.exec(text))) {
callback(match[1], key);
}
}
}
function getScriptMediaCandidates(platform) {
const result = [];
const seen = new Set();
const keysByPlatform = {
instagram: [
'video_url',
'contentUrl'
],
facebook: [
'playable_url_quality_hd',
'browser_native_hd_url',
'playable_url',
'browser_native_sd_url',
'progressive_url',
'hls_playlist_url',
'playable_url_dash',
'manifest_url'
]
};
const keys = keysByPlatform[platform] || [];
if (!keys.length) {
return result;
}
let scannedBytes = 0;
const maxTotalBytes = 14 * 1024 * 1024;
for (const script of document.scripts) {
if (scannedBytes >= maxTotalBytes) {
break;
}
const text = script.textContent || '';
if (!text || text.length > 5 * 1024 * 1024) {
continue;
}
if (!keys.some(key => text.includes(key))) {
continue;
}
scannedBytes += text.length;
extractKeyedUrlsFromText(text, keys, (url, key) => {
const kind = (
key.includes('hls')
? 'hls'
: key.includes('dash') || key.includes('manifest')
? classifyMediaUrl(url)
: 'direct'
);
addSourceRecord(result, seen, url, {
kind,
source: `${platform}-${key}`,
scoreBonus: (
/hd|quality_hd|progressive/i.test(key)
? 24_000
: 20_000
),
likelyMuxed: !(
key.includes('dash') ||
key.includes('manifest') ||
key.includes('hls')
),
headers: getPlatformRequestHeaders(platform)
});
});
// Instagram stores preferred progressive files inside
// video_versions arrays. Extract only URLs inside those blocks
// so image-version URLs are not mistaken for videos.
if (platform === 'instagram' && text.includes('video_versions')) {
const blockRegex = /["']video_versions["']\s*:\s*\[([\s\S]{0,350000}?)\]/gi;
let blockMatch;
while ((blockMatch = blockRegex.exec(text))) {
extractKeyedUrlsFromText(
blockMatch[1],
['url'],
url => {
addSourceRecord(result, seen, url, {
kind: 'direct',
source: 'instagram-video_versions',
scoreBonus: 26_000,
likelyMuxed: true,
headers: getPlatformRequestHeaders(platform)
});
}
);
}
}
}
return result;
}
function getYouTubePlayerResponses() {
const result = [];
const seen = new Set();
const add = value => {
const parsed = parseMaybeJson(value);
if (!parsed || typeof parsed !== 'object' || seen.has(parsed)) {
return;
}
seen.add(parsed);
result.push(parsed);
};
try {
add(PAGE.ytInitialPlayerResponse);
} catch {
// Ignore.
}
try {
add(PAGE.ytplayer?.config?.args?.raw_player_response);
add(PAGE.ytplayer?.config?.args?.player_response);
} catch {
// Ignore.
}
try {
const player = document.getElementById('movie_player');
add(player?.getPlayerResponse?.());
} catch {
// Ignore.
}
return result;
}
function getYouTubeFormatUrl(format) {
if (format?.url) {
return format.url;
}
const cipherText = format?.signatureCipher || format?.cipher || '';
if (!cipherText) {
return '';
}
try {
const cipher = new URLSearchParams(cipherText);
const baseUrl = cipher.get('url');
// A ciphered `s` parameter needs YouTube's changing player
// decipher logic. Do not save a broken URL; use fallback later.
if (!baseUrl || cipher.get('s')) {
return '';
}
const signature = cipher.get('sig') || cipher.get('signature');
if (!signature) {
return baseUrl;
}
const url = new URL(baseUrl);
url.searchParams.set(
cipher.get('sp') || 'signature',
signature
);
return url.href;
} catch {
return '';
}
}
function getYouTubeProgressiveCandidates() {
const result = [];
const seen = new Set();
for (const response of getYouTubePlayerResponses()) {
const formats = response?.streamingData?.formats || [];
for (const format of formats) {
const url = getYouTubeFormatUrl(format);
const mimeType = String(format?.mimeType || '');
const codecsMatch = mimeType.match(/codecs="([^"]+)"/i);
const codecCount = codecsMatch
? codecsMatch[1].split(',').length
: 0;
const hasAudio = Boolean(
format?.audioQuality ||
format?.audioSampleRate ||
format?.audioChannels ||
codecCount >= 2
);
if (
!url ||
!mimeType.toLowerCase().startsWith('video/') ||
!hasAudio
) {
continue;
}
const height = Number(format?.height || 0);
const width = Number(format?.width || 0);
const bitrate = Number(format?.bitrate || 0);
let qualityScore = height * 20 + Math.floor(bitrate / 100000);
if (preferredResolutionHeight > 0 && height > 0) {
qualityScore += Math.max(
0,
20_000 - Math.abs(height - preferredResolutionHeight) * 40
);
}
if (/video\/mp4/i.test(mimeType)) {
qualityScore += 2_000;
}
addSourceRecord(result, seen, url, {
kind: 'direct',
source: 'youtube-progressive-av',
contentType: mimeType.split(';')[0],
scoreBonus: 55_000 + qualityScore,
likelyMuxed: true,
headers: getPlatformRequestHeaders('youtube'),
width,
height,
bitrate
});
}
}
return result;
}
function getPlatformSpecificSourceCandidates(video) {
const platform = detectDownloadPlatform();
const result = [];
const seen = new Set();
const addAll = items => {
for (const item of items) {
if (!item?.url || seen.has(item.url)) {
continue;
}
seen.add(item.url);
result.push(item);
}
};
if (platform === 'youtube') {
addAll(getYouTubeProgressiveCandidates());
}
if (
platform === 'instagram' ||
platform === 'facebook'
) {
addAll(getMetaMediaCandidates(platform));
addAll(getJsonLdMediaCandidates(platform));
addAll(getScriptMediaCandidates(platform));
}
// A non-blob source exposed directly by the active social video
// is usually a progressive file and deserves a high score.
if (
platform &&
video?.currentSrc &&
!video.currentSrc.startsWith('blob:') &&
!isImageLikeUrl(video.currentSrc)
) {
addSourceRecord(result, seen, video.currentSrc, {
source: `${platform}-active-video`,
contentType: getVideoMimeType(video),
scoreBonus: 30_000,
likelyMuxed: platform !== 'youtube',
headers: getPlatformRequestHeaders(platform)
});
}
return result;
}
// ========================================================
// TRUSTED FACEBOOK REEL SOURCES (V9.7)
// ========================================================
function extractFacebookVideoIdFromUrl(value) {
if (!value) {
return '';
}
try {
const url = new URL(value, location.href);
const pathMatch = url.pathname.match(
/\/(?:reel|reels)\/(\d{5,30})(?:\/|$)/i
);
if (pathMatch) {
return pathMatch[1];
}
const videoPathMatch = url.pathname.match(
/\/videos\/(?:[^/]+\/)?(\d{5,30})(?:\/|$)/i
);
if (videoPathMatch) {
return videoPathMatch[1];
}
for (const key of ['v', 'video_id', 'story_fbid']) {
const id = normalizeFacebookVideoId(url.searchParams.get(key));
if (id) {
return id;
}
}
} catch {
// Ignore malformed URLs.
}
return '';
}
function isExactFacebookVideoRoute(value = location.href) {
try {
const url = new URL(value, location.href);
return Boolean(
/\/videos\/(?:[^/]+\/)?\d{5,30}(?:\/|$)/i.test(url.pathname)
);
} catch {
return false;
}
}
function extractFacebookStoryIdentityFromUrl(value) {
if (!value) {
return null;
}
try {
const url = new URL(value, location.href);
const pathMatch = url.pathname.match(
/\/stories\/(?:[^/?#]+\/)?(\d{5,30})(?:\/|$)/i
);
const storyId = normalizeFacebookVideoId(
pathMatch?.[1] ||
url.searchParams.get('story_fbid') ||
''
);
if (!storyId && !url.pathname.toLowerCase().includes('/stories/')) {
return null;
}
return {
id: '',
storyId,
surface: 'story',
url: url.href.split('#')[0]
};
} catch {
return null;
}
}
function getFacebookCurrentVideoIdentity(video) {
const candidates = [];
const seen = new Set();
const videoRect = video?.getBoundingClientRect?.() || null;
const storyRoute = extractFacebookStoryIdentityFromUrl(location.href);
const addIdentity = (identity, score = 0) => {
const id = normalizeFacebookVideoId(identity?.id);
const storyId = normalizeFacebookVideoId(
identity?.storyId || storyRoute?.storyId
);
const surface = identity?.surface || storyRoute?.surface || '';
const normalizedUrl = safeUrl(
identity?.url || storyRoute?.url || location.href
);
if (!id && !storyId) {
return;
}
const key = `${id}|${storyId}|${normalizedUrl}`;
if (seen.has(key)) {
return;
}
seen.add(key);
candidates.push({
id,
storyId,
surface,
url: normalizedUrl,
score
});
};
const add = (url, score = 0) => {
const normalizedUrl = safeUrl(url);
const id = extractFacebookVideoIdFromUrl(normalizedUrl);
if (!id) {
return;
}
addIdentity({
id,
url: normalizedUrl,
storyId: storyRoute?.storyId,
surface: storyRoute?.surface
}, score);
};
const addMappedSource = (url, score = 0) => {
const key = getInstagramUrlIndexKey(url);
const mapped = key ? facebookUrlIdentityStore.get(key) : null;
const id = normalizeFacebookVideoId(mapped?.id);
if (!id) {
return;
}
addIdentity({
id,
url: storyRoute?.url || location.href,
storyId: storyRoute?.storyId,
surface: storyRoute?.surface
}, score);
};
// The URL of the actual CDN file is a stronger identity than a
// Facebook Story route, because Story IDs and Video IDs can differ.
for (const sourceUrl of getVideoSourceCandidates(video)) {
addMappedSource(sourceUrl, 12000);
}
if (storyRoute) {
addIdentity(storyRoute, 2600);
} else {
// Exact /videos/<id>/ routes are authoritative. Comment videos
// often navigate here after the thumbnail is clicked, and a low
// route score let unrelated nearby post links win in V10.3.
add(
location.href,
isExactFacebookVideoRoute(location.href) ? 50000 : 1500
);
}
let element = video;
for (let depth = 0; element && depth < 12; depth += 1) {
if (
element === document.body ||
element === document.documentElement
) {
break;
}
for (const attribute of [
'data-video-id',
'data-videoid',
'data-ft',
'data-id'
]) {
const rawValue = element.getAttribute?.(attribute) || '';
const directId = normalizeFacebookVideoId(rawValue);
if (directId) {
addIdentity({
id: directId,
url: storyRoute?.url || `https://www.facebook.com/reel/${directId}/`,
storyId: storyRoute?.storyId,
surface: storyRoute?.surface
}, 4200 - depth * 40);
}
const embeddedId = rawValue.match(
/(?:video_id|videoId)["':=\s]+(\d{5,30})/i
)?.[1];
if (embeddedId) {
addIdentity({
id: embeddedId,
url: storyRoute?.url || `https://www.facebook.com/reel/${embeddedId}/`,
storyId: storyRoute?.storyId,
surface: storyRoute?.surface
}, 4000 - depth * 40);
}
}
let anchors = [];
try {
anchors = [...element.querySelectorAll('a[href]')].slice(0, 160);
} catch {
anchors = [];
}
for (const anchor of anchors) {
const href = anchor.href || anchor.getAttribute('href') || '';
const id = extractFacebookVideoIdFromUrl(href);
if (!id) {
continue;
}
let score = 850 - depth * 45;
try {
const rect = anchor.getBoundingClientRect();
if (videoRect) {
const overlaps = !(
rect.right < videoRect.left ||
rect.left > videoRect.right ||
rect.bottom < videoRect.top ||
rect.top > videoRect.bottom
);
if (overlaps) {
score += 2200;
} else if (rect.width > 0 && rect.height > 0) {
const anchorCenterX = rect.left + rect.width / 2;
const anchorCenterY = rect.top + rect.height / 2;
const videoCenterX = videoRect.left + videoRect.width / 2;
const videoCenterY = videoRect.top + videoRect.height / 2;
const distance = Math.hypot(
anchorCenterX - videoCenterX,
anchorCenterY - videoCenterY
);
score += Math.max(0, 900 - distance);
}
}
} catch {
// Ignore geometry failures.
}
add(href, score);
}
element = element.parentElement;
}
// V10.4: comment videos are often opened in an overlay while
// location.href remains the parent post/feed URL. Search Facebook
// video permalinks near the visible video by geometry so the normal
// proven GraphQL/current-page resolver can start immediately.
if (videoRect) {
let nearbyAnchors = [];
try {
nearbyAnchors = [...document.querySelectorAll(
'a[href*="/videos/"], a[href*="/reel/"], a[href*="/reels/"], ' +
'a[href*="watch?v="], a[href*="video.php"], a[href*="story_fbid="]'
)].slice(0, 900);
} catch {
nearbyAnchors = [];
}
for (const anchor of nearbyAnchors) {
const href = anchor.href || anchor.getAttribute('href') || '';
const id = extractFacebookVideoIdFromUrl(href);
if (!id) {
continue;
}
try {
const rect = anchor.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) {
continue;
}
const overlaps = !(
rect.right < videoRect.left ||
rect.left > videoRect.right ||
rect.bottom < videoRect.top ||
rect.top > videoRect.bottom
);
const anchorCenterX = rect.left + rect.width / 2;
const anchorCenterY = rect.top + rect.height / 2;
const videoCenterX = videoRect.left + videoRect.width / 2;
const videoCenterY = videoRect.top + videoRect.height / 2;
const distance = Math.hypot(
anchorCenterX - videoCenterX,
anchorCenterY - videoCenterY
);
if (
!overlaps &&
distance > Math.max(window.innerWidth, window.innerHeight) * 1.35
) {
continue;
}
add(
href,
(overlaps ? 3600 : 2400) +
Math.max(0, 1800 - distance * 1.15)
);
} catch {
// Ignore individual geometry failures.
}
}
}
candidates.sort((a, b) => b.score - a.score);
const bestIdentity = candidates[0] || {
id: '',
storyId: storyRoute?.storyId || '',
surface: storyRoute?.surface || '',
url: storyRoute?.url || location.href
};
// Facebook comment videos commonly open in a modal/theater while the
// browser URL stays on the parent feed/post. Mark this surface so the
// downloader can use the already-playing media source immediately
// instead of blocking on a slow page fetch.
const inOverlay = Boolean(
video?.closest?.('[role="dialog"], [aria-modal="true"]')
);
const currentRouteLooksVideoSpecific = Boolean(
/\/(?:reel|reels|watch|videos|stories)\//i.test(location.pathname) ||
/(?:^|[?&])(?:v|video_id|story_fbid)=\d+/i.test(location.search)
);
if (
inOverlay &&
!storyRoute &&
!currentRouteLooksVideoSpecific
) {
return {
...bestIdentity,
surface: 'comment-overlay'
};
}
if (
!storyRoute &&
isExactFacebookVideoRoute(location.href)
) {
return {
...bestIdentity,
id: extractFacebookVideoIdFromUrl(location.href) || bestIdentity.id,
url: location.href.split('#')[0],
surface: 'video-route'
};
}
return bestIdentity;
}
function getStoredFacebookCandidates(videoId) {
const result = [];
const seen = new Set();
const sources = facebookVideoSourceStore.get(videoId);
if (!sources) {
return result;
}
const cutoff = nowMs() - CONFIG.mediaUrlMaxAgeMs;
for (const record of sources.values()) {
if (Number(record.timestamp || 0) < cutoff) {
continue;
}
addSourceRecord(result, seen, record.url, {
kind: record.kind,
source: record.source,
contentType: record.contentType,
scoreBonus: Number(record.scoreBonus || 0) + 240000,
likelyMuxed: record.likelyMuxed,
headers: getPlatformRequestHeaders('facebook'),
width: record.width,
height: record.height,
bitrate: record.bitrate
});
}
return result;
}
function getFacebookRelevantTextWindows(text, targetVideoId) {
const source = String(text || '');
const maxWindow = 1_400_000;
if (!source) {
return [];
}
if (!targetVideoId) {
return [source.slice(0, Math.min(source.length, 8 * 1024 * 1024))];
}
const windows = [];
let startAt = 0;
while (windows.length < 18) {
const index = source.indexOf(targetVideoId, startAt);
if (index < 0) {
break;
}
const start = Math.max(0, index - maxWindow);
const end = Math.min(source.length, index + maxWindow);
windows.push(source.slice(start, end));
startAt = index + targetVideoId.length;
}
return windows.length ? windows : [];
}
function extractFacebookCandidatesFromText(
text,
targetVideoId,
sourcePrefix = 'facebook-scoped'
) {
const result = [];
const seen = new Set();
const sourceText = String(text || '');
if (!sourceText || sourceText.length > 40 * 1024 * 1024) {
return result;
}
// Parse data-sjs JSON blocks first. This mirrors the structured
// delivery fields used by Facebook's own page data.
const scriptRegex = /<script[^>]*data-sjs[^>]*>([\s\S]*?)<\/script>/gi;
let scriptMatch;
let parsedBlocks = 0;
while ((scriptMatch = scriptRegex.exec(sourceText)) && parsedBlocks < 120) {
const block = scriptMatch[1]
.replace(/"/g, '"')
.replace(/&/g, '&')
.trim();
if (targetVideoId && !block.includes(targetVideoId)) {
continue;
}
const parsed = parseFacebookJsonPayload(block);
if (parsed) {
extractFacebookDeliverySourcesFromObject(
parsed,
'',
`${sourcePrefix}-data-sjs`
);
parsedBlocks += 1;
}
}
for (const item of getStoredFacebookCandidates(targetVideoId)) {
addSourceRecord(result, seen, item.url, item);
}
// Fallback for script payloads that are not valid standalone JSON.
for (const windowText of getFacebookRelevantTextWindows(
sourceText,
targetVideoId
)) {
const specs = [
['playable_url_quality_hd', 'direct', 'hd', 270000],
['browser_native_hd_url', 'direct', 'hd', 265000],
['playable_url', 'direct', 'sd', 105000],
['browser_native_sd_url', 'direct', 'sd', 100000],
['hls_playlist_url', 'hls', '', 205000]
];
for (const [key, kind, quality, scoreBonus] of specs) {
extractKeyedUrlsFromText(windowText, [key], url => {
addSourceRecord(result, seen, url, {
kind,
source: `${sourcePrefix}-${key}`,
scoreBonus,
likelyMuxed: kind === 'direct',
headers: getPlatformRequestHeaders('facebook'),
height: quality === 'hd' ? 720 : quality === 'sd' ? 360 : 0
});
});
}
const progressiveRegex = /["']progressive_url["']\s*:\s*["']((?:\\.|[^"'\\])*)["']/gi;
let match;
while ((match = progressiveRegex.exec(windowText))) {
const nearby = windowText.slice(
Math.max(0, match.index - 1400),
Math.min(windowText.length, progressiveRegex.lastIndex + 1400)
);
const quality = nearby.match(
/["']quality["']\s*:\s*["']([^"']+)["']/i
)?.[1] || '';
const height = Number(
nearby.match(/["']height["']\s*:\s*(\d{3,4})/i)?.[1] ||
inferFacebookHeight(match[1], quality)
);
const isHd = /hd/i.test(quality) || height >= 720;
addSourceRecord(result, seen, match[1], {
kind: 'direct',
source: `${sourcePrefix}-progressive-${quality || height || 'unknown'}`,
scoreBonus: (
250000 +
(isHd ? 110000 : 20000) +
Math.min(120000, height * 80)
),
likelyMuxed: true,
headers: getPlatformRequestHeaders('facebook'),
height
});
}
}
return result;
}
function getFacebookScopedDomCandidates(video, targetVideoId) {
const result = [];
const seen = new Set();
let element = video;
let inspected = 0;
for (let depth = 0; element && depth < 10 && inspected < 5; depth += 1) {
let html = '';
try {
html = element.outerHTML || '';
} catch {
html = '';
}
if (
html &&
html.length <= 7 * 1024 * 1024 &&
(
html.includes('playable_url') ||
html.includes('progressive_url') ||
html.includes('videoDeliveryResponse')
)
) {
inspected += 1;
for (const item of extractFacebookCandidatesFromText(
html,
targetVideoId,
`facebook-active-dom-${depth}`
)) {
addSourceRecord(result, seen, item.url, item);
}
}
if (
element === document.body ||
element === document.documentElement
) {
break;
}
element = element.parentElement;
}
return result;
}
function getFacebookCurrentDocumentCandidates(targetVideoId) {
const id = normalizeFacebookVideoId(targetVideoId);
if (!id) {
return [];
}
const result = [];
const seen = new Set();
let inspected = 0;
for (const script of [...document.scripts].slice(0, 700)) {
const text = script.textContent || '';
if (
!text ||
text.length > 24 * 1024 * 1024 ||
!text.includes(id) ||
!(
text.includes('playable_url') ||
text.includes('progressive_url') ||
text.includes('videoDeliveryResponse') ||
text.includes('browser_native_hd_url')
)
) {
continue;
}
inspected += 1;
for (const item of extractFacebookCandidatesFromText(
text,
id,
`facebook-current-document-script-${inspected}`
)) {
addSourceRecord(result, seen, item.url, item);
}
if (inspected >= 30) {
break;
}
}
return result;
}
async function fetchFacebookCurrentPageCandidates(identity) {
if (!identity?.url || !identity?.id) {
return [];
}
try {
const response = await gmRequest({
method: 'GET',
url: identity.url,
headers: getPlatformRequestHeaders('facebook'),
responseType: 'text',
timeout: 6500
});
const body = response.responseText || response.response || '';
return extractFacebookCandidatesFromText(
body,
identity.id,
'facebook-current-page'
);
} catch (error) {
console.debug(
'[VRZ V10.4] Facebook current-page extraction failed:',
error
);
return [];
}
}
function scoreFacebookTrustedCandidate(source) {
let score = (
sourceBaseScore(source.kind, source.source || '') +
Number(source.scoreBonus || 0)
);
const height = Number(
source.height ||
inferFacebookHeight(`${source.source || ''} ${source.url || ''}`)
);
if (preferredResolutionHeight > 0 && height > 0) {
score += Math.max(
0,
180000 - Math.abs(height - preferredResolutionHeight) * 180
);
} else {
// No explicit quality selection: always prefer the highest
// verified progressive Facebook source available.
score += Math.min(300000, height * 220);
}
if (/quality_hd|native_hd|progressive-hd|\bhd\b/i.test(source.source || '')) {
score += 160000;
}
if (/\bsd\b|playable_url$/i.test(source.source || '')) {
score -= 45000;
}
if (source.likelyMuxed) {
score += 30000;
}
if (
source.kind === 'hls' &&
preferredResolutionHeight === 0
) {
score += 650000;
}
return score;
}
function getRecentFacebookStoryCandidates(video, identity = {}) {
if (identity.surface !== 'story') {
return [];
}
scanPerformanceMediaUrls();
const result = [];
const seen = new Set();
const now = nowMs();
const currentKey = getInstagramUrlIndexKey(
video?.currentSrc || video?.src || ''
);
for (const item of mediaUrlStore.values()) {
const age = now - Number(item.timestamp || 0);
if (age < 0 || age > 20000) {
continue;
}
if (!['direct', 'hls'].includes(item.kind)) {
continue;
}
let host = '';
try {
host = new URL(item.url).hostname.toLowerCase();
} catch {
continue;
}
if (
!host.includes('facebook') &&
!host.includes('fbcdn')
) {
continue;
}
const itemKey = getInstagramUrlIndexKey(item.url);
const activeMatch = Boolean(currentKey && itemKey === currentKey);
addSourceRecord(result, seen, item.url, {
...item,
source: activeMatch
? 'facebook-story-active-network-match'
: 'facebook-story-recent-network',
scoreBonus: (
(activeMatch ? 1_500_000 : 130000) +
Math.max(0, 20000 - age) * 8
),
likelyMuxed: item.kind === 'direct',
headers: getPlatformRequestHeaders('facebook'),
width: Number(video?.videoWidth || 0),
height: Number(video?.videoHeight || 0)
});
}
return result;
}
function getRecentFacebookActiveCandidates(video, identity = {}) {
scanPerformanceMediaUrls();
const result = [];
const seen = new Set();
const now = nowMs();
const currentKey = getInstagramUrlIndexKey(
video?.currentSrc || video?.src || ''
);
const isStory = identity.surface === 'story';
const maxAge = isStory ? 150_000 : 90_000;
for (const item of mediaUrlStore.values()) {
const age = now - Number(item.timestamp || 0);
if (age < 0 || age > maxAge) {
continue;
}
if (!['direct', 'hls', 'dash'].includes(item.kind)) {
continue;
}
let host = '';
try {
host = new URL(item.url).hostname.toLowerCase();
} catch {
continue;
}
if (
!host.includes('facebook') &&
!host.includes('fbcdn')
) {
continue;
}
const itemKey = getInstagramUrlIndexKey(item.url);
const activeMatch = Boolean(currentKey && itemKey === currentKey);
if (!activeMatch && age > (isStory ? 80_000 : 45_000)) {
continue;
}
addSourceRecord(result, seen, item.url, {
...item,
source: activeMatch
? (isStory ? 'facebook-story-active-network-match' : 'facebook-active-network-match')
: (isStory ? 'facebook-story-recent-network-visible' : 'facebook-recent-network-visible'),
scoreBonus: (
(activeMatch ? 1_900_000 : 0) +
(isStory ? 500_000 : 160_000) +
Math.max(0, maxAge - age) * 6
),
likelyMuxed: item.kind === 'direct',
headers: getPlatformRequestHeaders('facebook'),
width: Number(video?.videoWidth || 0),
height: Number(video?.videoHeight || 0)
});
}
return result;
}
async function getTrustedFacebookDownloadSources(video, options = {}) {
const identity = getFacebookCurrentVideoIdentity(video);
const result = [];
const byUrl = new Map();
const addAll = items => {
for (const item of items || []) {
if (!item?.url) {
continue;
}
const previous = byUrl.get(item.url);
if (
!previous ||
scoreFacebookTrustedCandidate(item) >
scoreFacebookTrustedCandidate(previous)
) {
byUrl.set(item.url, item);
}
}
};
// Fresh GraphQL delivery data for the current reel has top priority.
if (identity.id) {
addAll(getStoredFacebookCandidates(identity.id));
if (
identity.surface === 'video-route' ||
identity.surface === 'comment-overlay'
) {
addAll(getFacebookCurrentDocumentCandidates(identity.id));
}
}
addAll(getFacebookScopedDomCandidates(video, identity.id));
addAll(getRecentFacebookActiveCandidates(video, identity));
// The active media element is always associated with the reel the
// user currently sees, but it can be a lower playback rendition.
for (const url of getVideoSourceCandidates(video)) {
const kind = classifyMediaUrl(url, getVideoMimeType(video));
if (!['direct', 'hls', 'dash', 'blob'].includes(kind)) {
continue;
}
addAll([{
url,
kind,
source: 'facebook-current-visible-video',
contentType: getVideoMimeType(video),
scoreBonus: identity.surface === 'story' ? 1_800_000 : 190000,
likelyMuxed: kind === 'direct',
headers: getPlatformRequestHeaders('facebook'),
height: Number(video.videoHeight || 0),
width: Number(video.videoWidth || 0)
}]);
}
// V10.3 comment-video fast path. The currently playing source is
// already the media the user clicked. Do not await a 22-second page
// fetch when a strong direct/HLS source is available in the overlay.
const immediateCommentSources = [...byUrl.values()];
const hasImmediateCommentMedia = immediateCommentSources.some(source => (
(source.kind === 'direct' && source.likelyMuxed !== false) ||
source.kind === 'hls'
));
if (
(
identity.surface === 'comment-overlay' ||
identity.surface === 'video-route'
) &&
hasImmediateCommentMedia
) {
result.push(...immediateCommentSources);
result.sort((a, b) => (
scoreFacebookTrustedCandidate(b) -
scoreFacebookTrustedCandidate(a)
));
return result;
}
if (
identity.id &&
identity.url &&
options.fetchPage !== false
) {
addAll(await fetchFacebookCurrentPageCandidates(identity));
addAll(getStoredFacebookCandidates(identity.id));
}
if (identity.surface === 'story') {
addAll(getRecentFacebookStoryCandidates(video, identity));
}
addAll(getRecentFacebookActiveCandidates(video, identity));
// Recent network media is useful for a just-loaded reel, but keep
// the window short so the previous reel does not win after scroll.
const recentCutoff = nowMs() - 35_000;
for (const item of mediaUrlStore.values()) {
if (Number(item.timestamp || 0) < recentCutoff) {
continue;
}
if (!['direct', 'hls', 'dash'].includes(item.kind)) {
continue;
}
addAll([{
...item,
source: 'facebook-recent-network',
scoreBonus: 90000 + Math.max(
0,
35000 - Math.floor((nowMs() - item.timestamp) / 20)
),
likelyMuxed: item.kind === 'direct',
headers: getPlatformRequestHeaders('facebook')
}]);
}
// If Facebook did not expose an ID at all, retain the old universal
// ranking as a compatibility fallback.
if (!identity.id) {
addAll(getRankedVideoSources(video));
}
result.push(...byUrl.values());
result.sort((a, b) => (
scoreFacebookTrustedCandidate(b) -
scoreFacebookTrustedCandidate(a)
));
console.log('[VRZ V10.4] Facebook active video:', identity);
console.log('[VRZ V10.4] Facebook ranked sources:', result);
return result;
}
function hasStrongFacebookDownloadSource(sources) {
return (sources || []).some(source => {
const sourceName = String(source.source || '');
const height = Number(
source.height ||
inferFacebookHeight(`${sourceName} ${source.url || ''}`)
);
return Boolean(
source.kind === 'hls' ||
/quality_hd|native_hd|progressive-|graphql|current-page|active-dom|active-network|recent-network-visible|story-active-network-match|current-visible-video|current-document-script/i.test(sourceName) ||
(source.kind === 'direct' && source.likelyMuxed && height >= 540)
);
});
}
async function getFacebookDownloadSourcesWithWarmup(initialVideo) {
let video = initialVideo;
let bestSources = [];
let bestScore = -Infinity;
let activeTargetKey = '';
const fetchedTargetKeys = new Set();
const initialRouteId = normalizeFacebookVideoId(
extractFacebookVideoIdFromUrl(location.href)
);
const initialExactVideoRoute = Boolean(
initialRouteId && isExactFacebookVideoRoute(location.href)
);
const initialRoutePath = location.pathname;
const rounds = initialExactVideoRoute ? 10 : 12;
const targetChanged = () => {
if (!initialExactVideoRoute) {
return false;
}
const currentRouteId = normalizeFacebookVideoId(
extractFacebookVideoIdFromUrl(location.href)
);
return Boolean(
currentRouteId !== initialRouteId ||
!isExactFacebookVideoRoute(location.href) ||
location.pathname !== initialRoutePath
);
};
for (let round = 0; round < rounds; round += 1) {
if (targetChanged()) {
return {
video,
sources: [],
targetChanged: true
};
}
const freshVideo = getVideoForDownload('facebook');
if (freshVideo) {
video = freshVideo;
}
const identity = getFacebookCurrentVideoIdentity(video);
const targetKey = [
identity.id || '',
identity.storyId || '',
identity.surface || '',
getInstagramUrlIndexKey(video?.currentSrc || video?.src || ''),
location.pathname
].join('::');
if (activeTargetKey && targetKey !== activeTargetKey) {
bestSources = [];
bestScore = -Infinity;
}
activeTargetKey = targetKey;
// Exact /videos/<id>/ pages first get a short window to expose
// the already-playing current source or captured GraphQL data.
// Only then use a short current-page fetch as a fallback.
const pageFetchStartRound = initialExactVideoRoute ? 4 : 0;
const shouldFetchPage = Boolean(
identity.surface !== 'comment-overlay' &&
identity.id &&
identity.url &&
round >= pageFetchStartRound &&
!fetchedTargetKeys.has(targetKey)
);
if (shouldFetchPage) {
fetchedTargetKeys.add(targetKey);
}
const sources = await getTrustedFacebookDownloadSources(
video,
{
fetchPage: shouldFetchPage
}
);
if (targetChanged()) {
return {
video,
sources: [],
targetChanged: true
};
}
const score = sources.slice(0, 8).reduce(
(sum, source) => sum + scoreFacebookTrustedCandidate(source),
0
);
if (
sources.length > bestSources.length ||
score > bestScore
) {
bestSources = sources;
bestScore = score;
}
if (hasStrongFacebookDownloadSource(sources)) {
return {
video,
sources,
targetChanged: false
};
}
if (round < rounds - 1) {
showMessage(
identity.surface === 'story'
? `Waiting for Facebook Story source... ${round + 1}/${rounds - 1}`
: `Waiting for Facebook source... ${round + 1}/${rounds - 1}`,
3500
);
await delay(
initialExactVideoRoute
? (round < 4 ? 300 : 500)
: (round < 3 ? 450 : 700)
);
}
}
return {
video,
sources: bestSources,
targetChanged: false
};
}
function getPlayerDomMediaCandidates(video) {
const result = [];
const seen = new Set();
const attributes = [
'src',
'href',
'data-src',
'data-video-src',
'data-video-url',
'data-stream',
'data-stream-url',
'data-playback-url',
'data-content-url',
'data-hls',
'data-m3u8',
'data-mpd',
'data-manifest',
'data-file'
];
const add = value => {
const normalized = safeUrl(value);
if (
!normalized ||
normalized.startsWith('data:') ||
seen.has(normalized)
) {
return;
}
const kind = classifyMediaUrl(normalized);
if (!['direct', 'hls', 'dash', 'blob'].includes(kind)) {
return;
}
seen.add(normalized);
result.push(normalized);
};
const container = findPlayerContainer(video, {
ignoreFullscreen: true
});
if (!container) {
return result;
}
const nodes = [container];
try {
nodes.push(
...[...container.querySelectorAll(
'[src], [href], [data-src], [data-video-src], ' +
'[data-video-url], [data-stream], [data-stream-url], ' +
'[data-playback-url], [data-content-url], [data-hls], ' +
'[data-m3u8], [data-mpd], [data-manifest], [data-file]'
)].slice(0, 350)
);
} catch {
// Ignore.
}
for (const node of nodes) {
for (const attribute of attributes) {
try {
add(node.getAttribute?.(attribute));
} catch {
// Ignore.
}
}
}
return result;
}
function scanPerformanceMediaUrls() {
try {
for (const entry of performance.getEntriesByType('resource')) {
rememberMediaUrl(entry.name, {
source: 'performance-scan',
initiatorType: entry.initiatorType || ''
});
}
} catch {
// Ignore.
}
}
function sourceBaseScore(kind, source) {
const directSource = source.startsWith('video-');
if (directSource && kind === 'direct') return 25_000;
if (directSource && kind === 'hls') return 24_000;
if (directSource && kind === 'blob') return 8_000;
if (kind === 'direct') return 20_000;
if (kind === 'hls') return 19_000;
if (kind === 'dash') return 13_000;
if (kind === 'blob') return 7_000;
if (kind === 'segment') return 500;
return 0;
}
function getRankedVideoSources(video) {
scanPerformanceMediaUrls();
const candidates = new Map();
const addCandidate = (url, details = {}) => {
const normalized = decodeEscapedMediaUrl(url) || safeUrl(url);
if (
!normalized ||
normalized.startsWith('data:') ||
isImageLikeUrl(normalized)
) {
return;
}
const kind = details.kind || classifyMediaUrl(
normalized,
details.contentType,
details.initiatorType
);
if (
kind === 'unknown' ||
kind === 'segment' ||
kind === 'audio'
) {
return;
}
const source = details.source || 'unknown';
const age = Math.max(0, nowMs() - (details.timestamp || nowMs()));
const recencyBonus = Math.max(0, 5000 - Math.floor(age / 1000));
let score = (
sourceBaseScore(kind, source) +
recencyBonus +
Number(details.scoreBonus || 0)
);
if (normalized === video.currentSrc) {
score += 15_000;
}
if (normalized === video.src) {
score += 12_000;
}
if (details.likelyMuxed) {
score += 12_000;
}
if (/master|playlist|index|manifest/i.test(normalized)) {
score += 1_500;
}
if (/ad[sx]?[/_.-]|doubleclick|googlesyndication/i.test(normalized)) {
score -= 10_000;
}
const previous = candidates.get(normalized);
if (!previous || score > previous.score) {
candidates.set(normalized, {
url: normalized,
kind,
score,
contentType: details.contentType || '',
source,
likelyMuxed: Boolean(details.likelyMuxed),
headers: {
...(details.headers || {})
},
width: Number(details.width || 0),
height: Number(details.height || 0),
bitrate: Number(details.bitrate || 0)
});
}
};
// Platform-specific extraction is ranked first. These URLs come
// from progressive/muxed fields used by the platforms themselves.
for (const item of getPlatformSpecificSourceCandidates(video)) {
addCandidate(item.url, item);
}
const directCandidates = getVideoSourceCandidates(video);
directCandidates.forEach((url, index) => {
addCandidate(url, {
source: index === 0 ? 'video-currentSrc' : 'video-attribute',
contentType: getVideoMimeType(video),
likelyMuxed: false
});
});
getPlayerDomMediaCandidates(video).forEach(url => {
addCandidate(url, {
source: 'player-dom'
});
});
const cutoff = nowMs() - CONFIG.mediaUrlMaxAgeMs;
for (const item of mediaUrlStore.values()) {
if (item.timestamp < cutoff) {
continue;
}
addCandidate(item.url, item);
}
return [...candidates.values()]
.sort((a, b) => b.score - a.score);
}
function getVideoExtension(video, url, mimeType = '') {
try {
const pathname = new URL(url, location.href).pathname;
const match = pathname.match(/\.([a-zA-Z0-9]{2,5})$/);
if (
match &&
['mp4', 'webm', 'mov', 'mkv', 'm4v', 'ogv', 'ogg', 'ts']
.includes(match[1].toLowerCase())
) {
return match[1].toLowerCase();
}
} catch {
// Ignore.
}
const type = (mimeType || getVideoMimeType(video)).toLowerCase();
if (type.includes('webm')) return 'webm';
if (type.includes('ogg')) return 'ogv';
if (type.includes('quicktime')) return 'mov';
if (type.includes('x-matroska')) return 'mkv';
if (type.includes('mp2t')) return 'ts';
return 'mp4';
}
function buildDownloadFilename(video, url, mimeType = '', forcedExtension = '') {
const title = sanitizeFilename(document.title);
const extension = forcedExtension || getVideoExtension(
video,
url,
mimeType
);
return `${title}.${extension}`;
}
function fallbackDownload(source, filename) {
const link = document.createElement('a');
let objectUrl = null;
if (source instanceof Blob) {
objectUrl = URL.createObjectURL(source);
link.href = objectUrl;
} else {
link.href = source;
}
link.download = filename;
link.target = '_blank';
link.rel = 'noopener noreferrer';
ROOT.appendChild(link);
link.click();
link.remove();
if (objectUrl) {
setTimeout(() => URL.revokeObjectURL(objectUrl), 15000);
}
}
function isLikelyDownloadCancellation(error) {
const message = [
error?.error,
error?.details,
error?.message,
error?.statusText
].filter(Boolean).join(' ').toLowerCase();
return /cancel|cancelled|canceled|abort|aborted|user.*(?:deny|declin|stop)/i.test(message);
}
function startDirectDownload(source, filename, headers = {}) {
showMessage('Preparing download...', 5000);
return new Promise(resolve => {
try {
if (
typeof GM_download === 'function' &&
(typeof source === 'string' || source instanceof Blob)
) {
let settled = false;
const finish = result => {
if (settled) {
return;
}
settled = true;
resolve(result);
};
const downloadDetails = {
url: source,
name: filename,
saveAs: true,
onprogress: progress => {
try {
if (
progress?.lengthComputable &&
Number(progress.total) > 0
) {
const current = Math.max(
0,
Number(progress.loaded || 0)
);
const total = Number(progress.total);
showMessage(
`Downloading ${Math.min(current, total)}/${total}`,
5000
);
} else {
showMessage('Downloading...', 5000);
}
} catch {
showMessage('Downloading...', 5000);
}
},
onload: () => {
showMessage('Download complete');
finish({ status: 'success' });
},
onerror: error => {
console.error('[VRZ V10.4] GM_download stopped:', error);
// Tampermonkey reports both failure and user
// cancellation through onerror. Either way, do
// not automatically open another Save As dialog.
const cancelled = isLikelyDownloadCancellation(error);
showMessage(
cancelled
? 'Download canceled'
: 'Download stopped',
3000
);
finish({
status: cancelled ? 'cancelled' : 'failed',
error
});
},
ontimeout: () => {
showMessage('Download timed out');
finish({
status: 'failed',
error: new Error('Download timeout')
});
}
};
// Blob downloads are already complete local files. Do
// not attach request headers to them; headers only make
// sense when GM_download fetches a remote URL.
if (typeof source === 'string') {
downloadDetails.headers = {
...getPlatformRequestHeaders(),
...headers
};
}
GM_download(downloadDetails);
return;
}
fallbackDownload(source, filename);
showMessage('Download started');
resolve({ status: 'success' });
} catch (error) {
console.error('[VRZ V10.4] Direct download failed:', error);
resolve({ status: 'failed', error });
}
});
}
function gmRequest(options) {
return new Promise((resolve, reject) => {
if (typeof GM_xmlhttpRequest !== 'function') {
reject(new Error('GM_xmlhttpRequest is unavailable'));
return;
}
GM_xmlhttpRequest({
method: options.method || 'GET',
url: options.url,
headers: {
Referer: location.href,
...(options.headers || {})
},
responseType: options.responseType || 'text',
timeout: options.timeout || CONFIG.networkTimeoutMs,
anonymous: false,
onload: response => {
if (
response.status >= 200 &&
response.status < 400
) {
resolve(response);
} else {
reject(new Error(`HTTP ${response.status}`));
}
},
onerror: error => {
reject(new Error(
error?.error ||
error?.statusText ||
'Network error'
));
},
ontimeout: () => {
reject(new Error('Network timeout'));
}
});
});
}
function getResponseHeader(rawHeaders, name) {
const target = String(name || '').toLowerCase();
for (const line of String(rawHeaders || '').split(/\r?\n/)) {
const separator = line.indexOf(':');
if (separator <= 0) {
continue;
}
if (line.slice(0, separator).trim().toLowerCase() === target) {
return line.slice(separator + 1).trim();
}
}
return '';
}
function getExtensionFromMimeType(mimeType = '') {
const type = String(mimeType || '').toLowerCase();
if (type.includes('webm')) return 'webm';
if (type.includes('ogg')) return 'ogv';
if (type.includes('quicktime')) return 'mov';
if (type.includes('x-matroska')) return 'mkv';
if (type.includes('mp2t')) return 'ts';
if (type.includes('flv')) return 'flv';
if (type.startsWith('video/')) return 'mp4';
return '';
}
function isRejectedDownloadMime(mimeType = '') {
const type = String(mimeType || '')
.split(';')[0]
.trim()
.toLowerCase();
return (
type.startsWith('image/') ||
type.startsWith('audio/') ||
type.startsWith('text/') ||
type === 'application/json' ||
type === 'application/ld+json' ||
type === 'application/xml' ||
type === 'application/xhtml+xml'
);
}
function bytesToAscii(bytes, start, length) {
let result = '';
for (let index = start; index < start + length && index < bytes.length; index += 1) {
result += String.fromCharCode(bytes[index]);
}
return result;
}
function detectBinaryMedia(buffer) {
if (!(buffer instanceof ArrayBuffer) || buffer.byteLength < 4) {
return {
kind: 'unknown',
extension: ''
};
}
const bytes = new Uint8Array(buffer);
// JPEG / PNG / GIF / WebP are explicitly rejected. This fixes
// Facebook poster images being downloaded as .jfif files.
if (
(bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) ||
(
bytes[0] === 0x89 &&
bytes[1] === 0x50 &&
bytes[2] === 0x4e &&
bytes[3] === 0x47
) ||
bytesToAscii(bytes, 0, 3) === 'GIF' ||
(
bytesToAscii(bytes, 0, 4) === 'RIFF' &&
bytesToAscii(bytes, 8, 4) === 'WEBP'
)
) {
return {
kind: 'image',
extension: ''
};
}
if (
bytesToAscii(bytes, 4, 4) === 'ftyp' ||
bytesToAscii(bytes, 4, 4) === 'styp' ||
bytesToAscii(bytes, 4, 4) === 'moov' ||
bytesToAscii(bytes, 4, 4) === 'moof'
) {
return {
kind: 'video',
extension: 'mp4'
};
}
if (
bytes[0] === 0x1a &&
bytes[1] === 0x45 &&
bytes[2] === 0xdf &&
bytes[3] === 0xa3
) {
return {
kind: 'video',
extension: 'webm'
};
}
if (bytesToAscii(bytes, 0, 4) === 'OggS') {
return {
kind: 'video',
extension: 'ogv'
};
}
if (bytesToAscii(bytes, 0, 3) === 'FLV') {
return {
kind: 'video',
extension: 'flv'
};
}
if (
bytes.length > 376 &&
bytes[0] === 0x47 &&
bytes[188] === 0x47 &&
bytes[376] === 0x47
) {
return {
kind: 'video',
extension: 'ts'
};
}
const prefix = bytesToAscii(bytes, 0, Math.min(512, bytes.length))
.replace(/^\uFEFF/, '')
.trimStart();
if (prefix.startsWith('#EXTM3U')) {
return {
kind: 'hls',
extension: 'm3u8'
};
}
if (
/^<!doctype\s+html/i.test(prefix) ||
/^<html/i.test(prefix) ||
/^\{\s*["']/i.test(prefix) ||
/^\[\s*\{/i.test(prefix)
) {
return {
kind: 'document',
extension: ''
};
}
return {
kind: 'unknown',
extension: ''
};
}
function parseHttpContentRange(value) {
const match = String(value || '').match(
/bytes\s+(\d+)-(\d+)\/(\d+|\*)/i
);
if (!match) {
return null;
}
return {
start: Number(match[1]),
end: Number(match[2]),
total: match[3] === '*' ? 0 : Number(match[3])
};
}
function arrayBufferContainsAscii(buffer, token) {
if (!(buffer instanceof ArrayBuffer) || !token) {
return false;
}
const bytes = new Uint8Array(buffer);
const target = [...String(token)].map(char => char.charCodeAt(0));
outer:
for (let index = 0; index <= bytes.length - target.length; index += 1) {
for (let offset = 0; offset < target.length; offset += 1) {
if (bytes[index + offset] !== target[offset]) {
continue outer;
}
}
return true;
}
return false;
}
function validateCompleteInstagramFile(response, source) {
const buffer = response?.response;
const finalUrl = response?.finalUrl || response?.responseURL || source?.url || '';
const contentType = (
getResponseHeader(response?.responseHeaders, 'content-type') ||
source?.contentType ||
''
).split(';')[0].trim().toLowerCase();
const contentRange = parseHttpContentRange(
getResponseHeader(response?.responseHeaders, 'content-range')
);
const declaredContentLength = Number(
getResponseHeader(response?.responseHeaders, 'content-length') || 0
);
if (
!finalUrl ||
isPartialMediaUrl(finalUrl) ||
isImageLikeUrl(finalUrl) ||
isRejectedDownloadMime(contentType)
) {
return {
valid: false,
reason: 'partial or non-video Instagram response'
};
}
if (!(buffer instanceof ArrayBuffer) || buffer.byteLength < 4096) {
return {
valid: false,
reason: 'empty or too-small Instagram response'
};
}
if (
declaredContentLength > 0 &&
buffer.byteLength < declaredContentLength
) {
return {
valid: false,
reason: 'truncated Instagram response'
};
}
// A 206 response is only acceptable when the returned range covers
// the entire resource. This rejects Instagram playback fragments.
if (Number(response?.status) === 206) {
const completeRange = Boolean(
contentRange &&
contentRange.start === 0 &&
contentRange.total > 0 &&
contentRange.end + 1 >= contentRange.total
);
if (!completeRange) {
return {
valid: false,
reason: 'partial HTTP 206 Instagram response'
};
}
}
const detected = detectBinaryMedia(buffer);
if (detected.kind !== 'video') {
return {
valid: false,
reason: `Instagram payload is ${detected.kind || 'unknown'}, not video`
};
}
if (detected.extension === 'mp4') {
const bytes = new Uint8Array(buffer);
const hasFileTypeBox = (
bytesToAscii(bytes, 4, 4) === 'ftyp' ||
arrayBufferContainsAscii(buffer.slice(0, Math.min(buffer.byteLength, 512)), 'ftyp')
);
const hasMovieBox = arrayBufferContainsAscii(buffer, 'moov');
const hasMediaData = arrayBufferContainsAscii(buffer, 'mdat');
// A standalone Instagram MP4 must contain file metadata (ftyp),
// a movie description (moov), and actual media data (mdat). This
// rejects init-only and media-fragment responses that can still
// have an MP4 signature but open as a black/unplayable file.
if (!hasFileTypeBox || !hasMovieBox || !hasMediaData) {
return {
valid: false,
reason: 'incomplete MP4 fragment (missing ftyp/moov/mdat)'
};
}
}
return {
valid: true,
buffer,
finalUrl,
contentType: contentType || 'video/mp4',
extension: detected.extension || 'mp4'
};
}
function fetchCompleteInstagramFile(source) {
return new Promise((resolve, reject) => {
if (typeof GM_xmlhttpRequest !== 'function') {
reject(new Error('GM_xmlhttpRequest is unavailable'));
return;
}
showMessage('Downloading Instagram video...', 5000);
GM_xmlhttpRequest({
method: 'GET',
url: source.url,
headers: {
...getPlatformRequestHeaders('instagram'),
...(source.headers || {})
},
responseType: 'arraybuffer',
timeout: Math.max(CONFIG.networkTimeoutMs, 120000),
anonymous: false,
onprogress: progress => {
try {
if (
progress?.lengthComputable &&
Number(progress.total) > 0
) {
const loaded = Math.max(0, Number(progress.loaded || 0));
const total = Number(progress.total);
showMessage(
`Downloading ${Math.min(loaded, total)}/${total}`,
5000
);
} else {
showMessage('Downloading Instagram video...', 5000);
}
} catch {
showMessage('Downloading Instagram video...', 5000);
}
},
onload: response => {
if (
Number(response.status) < 200 ||
Number(response.status) >= 400
) {
reject(new Error(`HTTP ${response.status}`));
return;
}
const validation = validateCompleteInstagramFile(response, source);
if (!validation.valid) {
reject(new Error(validation.reason));
return;
}
resolve(validation);
},
onerror: error => {
reject(new Error(
error?.error ||
error?.statusText ||
'Instagram download request failed'
));
},
ontimeout: () => {
reject(new Error('Instagram download request timed out'));
}
});
});
}
function validatePlayableVideoBlob(blob, timeoutMs = 15000) {
return new Promise((resolve, reject) => {
const objectUrl = URL.createObjectURL(blob);
const testVideo = document.createElement('video');
let settled = false;
let timer = null;
const cleanup = () => {
clearTimeout(timer);
testVideo.removeAttribute('src');
try {
testVideo.load();
} catch {
// Ignore cleanup errors.
}
testVideo.remove();
URL.revokeObjectURL(objectUrl);
};
const finish = (ok, value) => {
if (settled) {
return;
}
settled = true;
cleanup();
if (ok) {
resolve(value);
} else {
reject(value instanceof Error ? value : new Error(String(value)));
}
};
const confirmDecodedFrame = () => {
if (testVideo.videoWidth > 0 && testVideo.videoHeight > 0) {
finish(true, {
width: testVideo.videoWidth,
height: testVideo.videoHeight,
duration: Number(testVideo.duration || 0)
});
}
};
testVideo.preload = 'auto';
testVideo.muted = true;
testVideo.playsInline = true;
testVideo.style.cssText = [
'position:fixed',
'left:-99999px',
'top:-99999px',
'width:1px',
'height:1px',
'opacity:0',
'pointer-events:none'
].join(';');
testVideo.addEventListener('loadeddata', confirmDecodedFrame);
testVideo.addEventListener('canplay', confirmDecodedFrame);
testVideo.addEventListener('error', () => {
finish(false, new Error('browser could not decode Instagram video'));
}, { once: true });
timer = setTimeout(() => {
finish(false, new Error('Instagram video decode validation timed out'));
}, timeoutMs);
ROOT.appendChild(testVideo);
testVideo.src = objectUrl;
try {
testVideo.load();
} catch (error) {
finish(false, error);
}
});
}
async function downloadVerifiedInstagramSource(video, source) {
if (
!source?.url ||
isPartialMediaUrl(source.url) ||
source.kind !== 'direct'
) {
return {
status: 'failed',
error: new Error('Rejected Instagram fragment source')
};
}
try {
const file = await fetchCompleteInstagramFile(source);
const filename = buildDownloadFilename(
video,
file.finalUrl,
file.contentType,
file.extension
);
const blob = new Blob([file.buffer], {
type: file.contentType
});
showMessage('Validating Instagram video...', 5000);
await validatePlayableVideoBlob(blob);
const saveResult = await startDirectDownload(blob, filename, {});
return {
...saveResult,
promptShown: true
};
} catch (error) {
console.warn(
'[VRZ V10.4] Instagram candidate rejected before save:',
source,
error
);
return {
status: 'failed',
error
};
}
}
async function probeMediaCandidate(source) {
const url = source?.url || String(source || '');
const headers = {
...getPlatformRequestHeaders(),
...(source?.headers || {})
};
if (!url || isImageLikeUrl(url) || isPartialMediaUrl(url)) {
return {
valid: false,
reason: isPartialMediaUrl(url) ? 'partial media URL' : 'image URL'
};
}
let contentType = String(source?.contentType || '')
.split(';')[0]
.trim()
.toLowerCase();
let finalUrl = url;
try {
const head = await gmRequest({
method: 'HEAD',
url,
headers,
responseType: 'text',
timeout: 15000
});
finalUrl = head.finalUrl || head.responseURL || finalUrl;
contentType = (
getResponseHeader(head.responseHeaders, 'content-type') ||
contentType
).split(';')[0].trim().toLowerCase();
if (
detectDownloadPlatform() === 'instagram' &&
(
isPartialMediaUrl(finalUrl) ||
Number(head.status) === 206 ||
Boolean(getResponseHeader(head.responseHeaders, 'content-range'))
)
) {
return {
valid: false,
reason: 'partial Instagram response'
};
}
if (
isImageLikeUrl(finalUrl) ||
isRejectedDownloadMime(contentType)
) {
return {
valid: false,
reason: `rejected MIME ${contentType || 'unknown'}`
};
}
if (
contentType.includes('mpegurl') ||
contentType.includes('vnd.apple.mpegurl')
) {
return {
valid: true,
kind: 'hls',
contentType,
finalUrl
};
}
if (contentType.includes('dash+xml')) {
return {
valid: true,
kind: 'dash',
contentType,
finalUrl
};
}
if (
contentType.startsWith('video/') &&
detectDownloadPlatform() !== 'instagram'
) {
return {
valid: true,
kind: 'direct',
contentType,
extension: getExtensionFromMimeType(contentType),
finalUrl
};
}
// Instagram is intentionally not accepted from HEAD alone.
// Continue to the byte probe below so a misleading MIME type,
// cover image, HTML response, or non-standalone fragment is
// rejected before GM_download starts.
if (
contentType === 'application/octet-stream' &&
isVideoLikeUrl(finalUrl)
) {
return {
valid: true,
kind: 'direct',
contentType,
extension: getVideoExtension(null, finalUrl, contentType),
finalUrl
};
}
} catch (error) {
console.debug('[VRZ V10.4] HEAD probe failed:', url, error);
}
const shouldRangeProbe = Boolean(
source?.likelyMuxed ||
source?.source?.includes('youtube-progressive') ||
source?.source?.includes('instagram-') ||
source?.source?.includes('facebook-') ||
isVideoLikeUrl(url)
);
if (!shouldRangeProbe) {
return {
valid: false,
reason: 'unverified source'
};
}
try {
const probe = await gmRequest({
url,
headers: {
...headers,
Range: 'bytes=0-65535'
},
responseType: 'arraybuffer',
timeout: 20000
});
finalUrl = probe.finalUrl || probe.responseURL || finalUrl;
contentType = (
getResponseHeader(probe.responseHeaders, 'content-type') ||
contentType
).split(';')[0].trim().toLowerCase();
if (
isImageLikeUrl(finalUrl) ||
isRejectedDownloadMime(contentType)
) {
return {
valid: false,
reason: `rejected MIME ${contentType || 'unknown'}`
};
}
const detected = detectBinaryMedia(probe.response);
if (detected.kind === 'image' || detected.kind === 'document') {
return {
valid: false,
reason: `${detected.kind} response`
};
}
if (detected.kind === 'hls') {
return {
valid: true,
kind: 'hls',
contentType: contentType || 'application/vnd.apple.mpegurl',
finalUrl
};
}
if (detected.kind === 'video') {
return {
valid: true,
kind: 'direct',
contentType: contentType || 'video/mp4',
extension: detected.extension,
finalUrl
};
}
if (contentType.startsWith('video/')) {
return {
valid: true,
kind: 'direct',
contentType,
extension: getExtensionFromMimeType(contentType),
finalUrl
};
}
} catch (error) {
console.debug('[VRZ V10.4] Range probe failed:', url, error);
}
return {
valid: false,
reason: 'not a verified video file'
};
}
async function fetchText(url) {
try {
const response = await gmRequest({
url,
responseType: 'text'
});
return response.responseText || response.response || '';
} catch (gmError) {
try {
const response = await fetch(url, {
credentials: 'include'
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.text();
} catch {
throw gmError;
}
}
}
async function fetchArrayBuffer(url, headers = {}) {
try {
const response = await gmRequest({
url,
headers,
responseType: 'arraybuffer'
});
return response.response;
} catch (gmError) {
try {
const response = await fetch(url, {
credentials: 'include',
headers
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.arrayBuffer();
} catch {
throw gmError;
}
}
}
// ========================================================
// DOWNLOAD NON-DRM HLS (.M3U8)
// ========================================================
function parseAttributeList(value) {
const result = {};
const regex = /([A-Z0-9-]+)=((?:"[^"]*")|(?:[^,]*))/gi;
let match;
while ((match = regex.exec(value))) {
let data = match[2].trim();
if (data.startsWith('"') && data.endsWith('"')) {
data = data.slice(1, -1);
}
result[match[1].toUpperCase()] = data;
}
return result;
}
function parseHlsMaster(text, baseUrl) {
const lines = text
.split(/\r?\n/)
.map(line => line.trim())
.filter(Boolean);
const variants = [];
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index];
if (!line.startsWith('#EXT-X-STREAM-INF:')) {
continue;
}
const attrs = parseAttributeList(
line.slice('#EXT-X-STREAM-INF:'.length)
);
let uri = '';
for (let next = index + 1; next < lines.length; next += 1) {
if (!lines[next].startsWith('#')) {
uri = lines[next];
break;
}
}
if (!uri) {
continue;
}
const resolution = attrs.RESOLUTION || '';
const match = resolution.match(/(\d+)x(\d+)/i);
const width = match ? Number(match[1]) : 0;
const height = match ? Number(match[2]) : 0;
const pixels = width * height;
variants.push({
url: safeUrl(uri, baseUrl),
bandwidth: Number(attrs.BANDWIDTH || 0),
averageBandwidth: Number(attrs['AVERAGE-BANDWIDTH'] || 0),
width,
height,
pixels,
name: attrs.NAME || '',
codecs: attrs.CODECS || '',
frameRate: Number(attrs['FRAME-RATE'] || 0),
hasExternalAudio: Boolean(attrs.AUDIO)
});
}
variants.sort((a, b) => {
// Prefer muxed variants when available, then the highest quality.
if (a.hasExternalAudio !== b.hasExternalAudio) {
return Number(a.hasExternalAudio) - Number(b.hasExternalAudio);
}
return (
(b.pixels - a.pixels) ||
(b.bandwidth - a.bandwidth)
);
});
return variants;
}
function parseByteRange(value, previousEnd = 0) {
const match = String(value || '').match(/^(\d+)(?:@(\d+))?$/);
if (!match) {
return null;
}
const length = Number(match[1]);
const start = match[2] !== undefined
? Number(match[2])
: previousEnd;
return {
start,
end: start + length - 1,
nextEnd: start + length
};
}
function parseHlsMediaPlaylist(text, baseUrl) {
const lines = text
.split(/\r?\n/)
.map(line => line.trim());
const segments = [];
let map = null;
let pendingByteRange = null;
let previousByteRangeEnd = 0;
let encrypted = false;
let encryptionMethod = 'NONE';
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index];
if (!line) {
continue;
}
if (line.startsWith('#EXT-X-KEY:')) {
const attrs = parseAttributeList(
line.slice('#EXT-X-KEY:'.length)
);
encryptionMethod = (attrs.METHOD || 'NONE').toUpperCase();
if (encryptionMethod !== 'NONE') {
encrypted = true;
}
continue;
}
if (line.startsWith('#EXT-X-MAP:')) {
const attrs = parseAttributeList(
line.slice('#EXT-X-MAP:'.length)
);
if (attrs.URI) {
map = {
url: safeUrl(attrs.URI, baseUrl),
byteRange: attrs.BYTERANGE || ''
};
}
continue;
}
if (line.startsWith('#EXT-X-BYTERANGE:')) {
pendingByteRange = line.slice('#EXT-X-BYTERANGE:'.length);
continue;
}
if (line.startsWith('#')) {
continue;
}
let byteRange = null;
if (pendingByteRange) {
byteRange = parseByteRange(
pendingByteRange,
previousByteRangeEnd
);
if (byteRange) {
previousByteRangeEnd = byteRange.nextEnd;
}
}
segments.push({
url: safeUrl(line, baseUrl),
byteRange
});
pendingByteRange = null;
}
return {
segments,
map,
encrypted,
encryptionMethod,
hasEndList: text.includes('#EXT-X-ENDLIST')
};
}
async function resolveHlsPlaylist(manifestUrl, targetHeight = 0) {
let currentUrl = manifestUrl;
let text = await fetchText(currentUrl);
let hasExternalAudio = false;
const selectedVariants = [];
for (let depth = 0; depth < 4; depth += 1) {
const variants = parseHlsMaster(text, currentUrl);
if (!variants.length) {
return {
url: currentUrl,
text,
hasExternalAudio,
selectedVariants
};
}
let selectedVariant = [...variants].sort((a, b) => {
// Prefer a muxed variant because this userscript cannot
// remux separate audio/video tracks inside the browser.
if (a.hasExternalAudio !== b.hasExternalAudio) {
return Number(a.hasExternalAudio) - Number(b.hasExternalAudio);
}
if (targetHeight > 0) {
const aDistance = Math.abs((a.height || 0) - targetHeight);
const bDistance = Math.abs((b.height || 0) - targetHeight);
return (
(aDistance - bDistance) ||
((b.height || 0) - (a.height || 0)) ||
((b.bandwidth || 0) - (a.bandwidth || 0))
);
}
// AUTO means best quality: highest resolution, then bitrate.
return (
((b.height || 0) - (a.height || 0)) ||
((b.width || 0) - (a.width || 0)) ||
((b.bandwidth || 0) - (a.bandwidth || 0))
);
})[0];
selectedVariants.push(selectedVariant);
hasExternalAudio = (
hasExternalAudio ||
Boolean(selectedVariant.hasExternalAudio)
);
currentUrl = selectedVariant.url;
text = await fetchText(currentUrl);
}
throw new Error('Too many nested HLS playlist levels');
}
async function createDownloadSink(filename, mimeType) {
try {
const picker = PAGE.showSaveFilePicker;
if (
typeof picker === 'function' &&
window.isSecureContext
) {
const handle = await Reflect.apply(picker, PAGE, [{
suggestedName: filename
}]);
const writable = await handle.createWritable();
return {
mode: 'file-system',
async write(chunk) {
await writable.write(chunk);
},
async close() {
await writable.close();
}
};
}
} catch (error) {
if (
error?.name === 'AbortError' ||
error?.name === 'NotAllowedError'
) {
// Abort = user canceled. NotAllowed = fall back to Blob.
if (error?.name === 'AbortError') {
return null;
}
} else {
console.warn('[VRZ V10.4] File System Access failed:', error);
}
}
const chunks = [];
return {
mode: 'memory',
async write(chunk) {
chunks.push(chunk);
},
async close() {
const blob = new Blob(chunks, {
type: mimeType
});
fallbackDownload(blob, filename);
}
};
}
async function downloadHls(video, manifestUrl) {
showMessage('Preparing HLS download...');
const resolved = await resolveHlsPlaylist(
manifestUrl,
preferredResolutionHeight
);
const playlist = parseHlsMediaPlaylist(
resolved.text,
resolved.url
);
if (resolved.hasExternalAudio) {
throw new Error('HLS uses separate audio/video tracks');
}
if (!playlist.segments.length) {
throw new Error('No HLS segments found');
}
if (playlist.encrypted) {
throw new Error(
`Encrypted HLS (${playlist.encryptionMethod})`
);
}
const firstSegmentUrl = playlist.segments[0].url;
const useMp4 = Boolean(
playlist.map ||
/\.(m4s|mp4|cmfv)(?:$|[?#])/i.test(firstSegmentUrl)
);
const extension = useMp4 ? 'mp4' : 'ts';
const mimeType = useMp4 ? 'video/mp4' : 'video/mp2t';
const filename = buildDownloadFilename(
video,
manifestUrl,
mimeType,
extension
);
const sink = await createDownloadSink(filename, mimeType);
if (!sink) {
showMessage('Download canceled');
return true;
}
showMessage(`Downloading 0/${playlist.segments.length}`, 5000);
if (playlist.map) {
const headers = {};
if (playlist.map.byteRange) {
const range = parseByteRange(playlist.map.byteRange, 0);
if (range) {
headers.Range = `bytes=${range.start}-${range.end}`;
}
}
const initBuffer = await fetchArrayBuffer(
playlist.map.url,
headers
);
await sink.write(initBuffer);
}
for (let index = 0; index < playlist.segments.length; index += 1) {
const segment = playlist.segments[index];
const headers = {};
if (segment.byteRange) {
headers.Range = (
`bytes=${segment.byteRange.start}-${segment.byteRange.end}`
);
}
const buffer = await fetchArrayBuffer(
segment.url,
headers
);
await sink.write(buffer);
const current = index + 1;
if (
current === playlist.segments.length ||
current % 3 === 0
) {
showMessage(
`Downloading ${current}/${playlist.segments.length}`,
5000
);
}
}
await sink.close();
showMessage('Download complete');
return true;
}
async function downloadBlobSource(video, url) {
showMessage('Reading video blob...');
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const blob = await response.blob();
if (!blob.size) {
throw new Error('Empty blob');
}
const mimeType = String(blob.type || '')
.split(';')[0]
.trim()
.toLowerCase();
if (isRejectedDownloadMime(mimeType)) {
throw new Error(`Rejected blob MIME: ${mimeType}`);
}
const sample = await blob.slice(0, 65536).arrayBuffer();
const detected = detectBinaryMedia(sample);
if (
detected.kind !== 'video' &&
!mimeType.startsWith('video/')
) {
throw new Error(
'Blob is a MediaSource/stream, not a standalone video file'
);
}
const filename = buildDownloadFilename(
video,
url,
mimeType,
detected.extension || getExtensionFromMimeType(mimeType)
);
fallbackDownload(blob, filename);
showMessage('Download started');
return true;
} catch (error) {
console.warn('[VRZ V10.4] Blob is not a direct file:', error);
return false;
}
}
function hasStrongInstagramDownloadSource(sources, identity = {}) {
if (identity.surface === 'story') {
return (sources || []).some(source => (
source.kind === 'direct' &&
source.likelyMuxed !== false &&
/instagram-story-(?:exact|current-visible-video)/i.test(
String(source.source || '')
)
));
}
return (sources || []).some(source => (
source.kind === 'direct' &&
source.likelyMuxed !== false &&
/instagram-(?:api|media-info)|video_versions|instagram-active-progressive|active-match|active-network-match|identity-network-match|feed-visible-progressive|active-dom-identity-video/i.test(
String(source.source || '')
)
));
}
function getInstagramTopCandidateScore(sources, identity = {}) {
const top = [...(sources || [])]
.sort((a, b) => compareInstagramDownloadSources(a, b, identity))[0];
if (!top) {
return -Infinity;
}
return (
getInstagramSourceTrust(top) * 1_000_000_000_000 +
Number(top.height || 0) * 1_000_000 +
Number(top.width || 0) * 1_000 +
Math.min(999, Math.round(Number(top.bitrate || 0) / 1000))
);
}
async function getInstagramDownloadSourcesWithWarmup(initialVideo) {
let video = initialVideo;
let bestSources = [];
let bestIdentity = getInstagramCurrentMediaIdentity(initialVideo);
let bestScore = -Infinity;
const routeStory = extractInstagramStoryIdentityFromUrl(location.href);
const routeMedia = extractInstagramIdentityFromUrl(location.href);
const lockedIdentity = routeStory?.surface === 'story'
? routeStory
: (routeMedia?.shortcode
? { ...routeMedia, surface: 'media-route' }
: null);
const lockedStoryId = normalizeInstagramMediaId(lockedIdentity?.storyId);
const lockedStoryUsername = normalizeInstagramUsername(lockedIdentity?.username);
const lockedShortcode = normalizeInstagramShortcode(lockedIdentity?.shortcode);
const stableClickedVideo = lockedShortcode ? null : initialVideo;
const attemptedApiIdentities = new Set();
const storyApiAttempts = new Map();
const isLockedStorySurface = lockedIdentity?.surface === 'story';
const rounds = isLockedStorySurface ? 8 : 6;
for (let round = 0; round < rounds; round += 1) {
// V10.4: generic feed downloads keep the exact video element
// that was visible when the user clicked Download. This restores
// V9.6 behavior and prevents Instagram preload elements from
// stealing the target during warmup.
const stableStillUsable = Boolean(
stableClickedVideo &&
stableClickedVideo.isConnected &&
isVideoVisible(stableClickedVideo)
);
const freshVideo = stableStillUsable
? stableClickedVideo
: getVideoForDownload('instagram');
if (freshVideo) {
video = freshVideo;
}
// Exact route targets are immutable for this click.
if (lockedIdentity?.surface === 'story') {
const currentStory = extractInstagramStoryIdentityFromUrl(location.href);
const currentStoryId = normalizeInstagramMediaId(currentStory?.storyId);
const currentUsername = normalizeInstagramUsername(currentStory?.username);
if (
currentStory?.surface !== 'story' ||
(lockedStoryId && currentStoryId && currentStoryId !== lockedStoryId) ||
(lockedStoryUsername && currentUsername && currentUsername !== lockedStoryUsername)
) {
return {
video,
sources: [],
identity: lockedIdentity,
targetChanged: true
};
}
}
if (lockedShortcode) {
const currentMedia = extractInstagramIdentityFromUrl(location.href);
if (
!currentMedia?.shortcode ||
normalizeInstagramShortcode(currentMedia.shortcode) !== lockedShortcode
) {
return {
video,
sources: [],
identity: lockedIdentity,
targetChanged: true
};
}
}
const identity = lockedIdentity || getInstagramCurrentMediaIdentity(video);
const regularIdentityKey = instagramIdentityKeys(identity).join('|');
const storyWarmupKey = identity.surface === 'story'
? getInstagramStoryWarmupKey(identity)
: '';
const identityKey = regularIdentityKey || storyWarmupKey;
const storyAttemptCount = Number(
storyApiAttempts.get(storyWarmupKey || 'story-route') || 0
);
const useApi = identity.surface === 'story'
? Boolean(
storyWarmupKey &&
storyAttemptCount < 2
)
: Boolean(
identityKey &&
!attemptedApiIdentities.has(identityKey)
);
if (useApi && identity.surface === 'story') {
storyApiAttempts.set(
storyWarmupKey || 'story-route',
storyAttemptCount + 1
);
} else if (useApi) {
attemptedApiIdentities.add(identityKey);
}
const sources = await getTrustedInstagramDownloadSources(
video,
{
identity,
useApi
}
);
const score = sources.slice(0, 8).reduce(
(sum, source) => (
sum +
Number(source.scoreBonus || 0) +
Number(source.height || 0) * 1000
),
0
);
if (score > bestScore || sources.length > bestSources.length) {
bestSources = sources;
bestIdentity = identity;
bestScore = score;
}
if (hasStrongInstagramDownloadSource(sources, identity)) {
return {
video,
sources,
identity,
targetChanged: false
};
}
if (round < rounds - 1) {
showMessage(
identity.surface === 'story'
? `Finding current Instagram Story... ${round + 1}/${rounds - 1}`
: `Finding current Instagram video... ${round + 1}/${rounds - 1}`,
3200
);
await delay(round < 2 ? 300 : 550);
}
}
if (
!lockedIdentity &&
initialVideo &&
video !== initialVideo &&
!isVideoVisible(initialVideo) &&
!bestSources.length
) {
return {
video,
sources: [],
identity: bestIdentity,
targetChanged: true
};
}
return {
video,
sources: bestSources,
identity: bestIdentity,
targetChanged: false
};
}
async function downloadInstagramVideoV103(video, sources, identity = {}) {
if (!Array.isArray(sources) || !sources.length) {
showMessage('Instagram video source not found. Play the visible item and retry.', 6000);
return;
}
const isStory = identity.surface === 'story';
const expectedStoryId = normalizeInstagramMediaId(
identity.storyId || identity.mediaId
);
const expectedShortcode = normalizeInstagramShortcode(identity.shortcode);
const routeLockedShortcode = identity.surface === 'media-route'
? expectedShortcode
: '';
let lastError = null;
for (const source of sources.slice(0, 20)) {
try {
if (
source.kind !== 'direct' ||
source.likelyMuxed === false ||
isInstagramFragmentUrl(source.url)
) {
continue;
}
// Re-check the locked target before every expensive request.
if (isStory && expectedStoryId) {
const currentStory = extractInstagramStoryIdentityFromUrl(location.href);
if (
!currentStory?.storyId ||
normalizeInstagramMediaId(currentStory.storyId) !== expectedStoryId
) {
showMessage('Instagram Story changed. Click Download again.', 5000);
return;
}
}
if (!isStory && routeLockedShortcode) {
const currentMedia = extractInstagramIdentityFromUrl(location.href);
if (
!currentMedia?.shortcode ||
normalizeInstagramShortcode(currentMedia.shortcode) !== routeLockedShortcode
) {
showMessage('Instagram post changed. Click Download again.', 5000);
return;
}
}
if (isStory) {
// V10.0's full-file verification is kept ONLY for Story,
// because that was the part the user confirmed could work.
// Exact target locking above fixes the wrong-person bug.
const result = await downloadVerifiedInstagramSource(video, source);
if (result.promptShown) {
// Critical V10.1 rule: once Save As was opened, this
// click is finished. Cancel/error must never try the
// next candidate and open Save As again.
return;
}
if (result.status === 'success' || result.status === 'cancelled') {
return;
}
lastError = result.error || lastError;
continue;
}
// Normal posts/Home/Explore/Profile/Reels use the proven
// V9.5/V9.6 direct-source flow. Validate before showing a
// dialog, then invoke exactly one Save As prompt.
showMessage('Checking Instagram video source...', 5000);
const probe = await probeInstagramCandidateV101(source);
if (!probe.valid) {
console.debug(
'[VRZ V10.4] Rejected Instagram candidate:',
source,
probe.reason
);
continue;
}
if (probe.kind === 'dash') {
continue;
}
if (probe.kind === 'hls') {
await downloadHls(video, probe.finalUrl || source.url);
return;
}
const finalUrl = probe.finalUrl || source.url;
const filename = buildDownloadFilename(
video,
finalUrl,
probe.contentType || source.contentType,
probe.extension || ''
);
// One click = one Save As prompt. Whatever GM_download
// reports after this call, do not continue to another URL.
await startDirectDownload(
finalUrl,
filename,
source.headers
);
return;
} catch (error) {
lastError = error;
console.warn('[VRZ V10.4] Instagram candidate failed:', source, error);
}
}
showMessage(
lastError
? 'Instagram download failed. Play/reopen the current video and retry.'
: 'Instagram video source not found. Play the visible item and retry.',
6500
);
}
async function downloadVideo() {
if (downloadAttemptActive) {
showMessage('Download already in progress', 2500);
return;
}
downloadAttemptActive = true;
try {
const platform = detectDownloadPlatform();
let video = getVideoForDownload(platform);
if (!video) {
if (
platform === 'youtube' &&
openExternalDownloadFallback(platform, 'video element not found')
) {
return;
}
showMessage('Video not found');
return;
}
let sources = [];
let instagramIdentity = null;
if (platform === 'instagram') {
const resolved = await getInstagramDownloadSourcesWithWarmup(video);
video = resolved.video || video;
sources = resolved.sources;
instagramIdentity = resolved.identity || null;
if (resolved.targetChanged) {
showMessage('Instagram item changed. Click Download again.', 5000);
return;
}
} else if (platform === 'facebook') {
const resolved = await getFacebookDownloadSourcesWithWarmup(video);
video = resolved.video || video;
sources = resolved.sources;
if (resolved.targetChanged) {
showMessage('Facebook video changed. Click Download again.', 4500);
return;
}
} else {
sources = getRankedVideoSources(video);
}
console.log('[VRZ V10.4] Video source candidates:', sources);
if (platform === 'instagram') {
await downloadInstagramVideoV103(
video,
sources,
instagramIdentity || getInstagramCurrentMediaIdentity(video)
);
return;
}
let lastError = null;
let sawDash = false;
let sawSeparateTracks = false;
let checkedCount = 0;
for (const source of sources.slice(0, 28)) {
try {
if (
platform === 'youtube' &&
source.kind === 'direct' &&
!source.likelyMuxed
) {
continue;
}
if (
platform === 'instagram' &&
source.kind === 'direct' &&
(
!source.likelyMuxed ||
isPartialMediaUrl(source.url)
)
) {
continue;
}
if (source.kind === 'direct') {
checkedCount += 1;
// Instagram is downloaded into memory first, then the
// COMPLETE file is validated before a single Save As
// prompt is shown. This prevents black-screen MP4
// fragments from ever reaching the user's disk.
if (platform === 'instagram') {
const result = await downloadVerifiedInstagramSource(
video,
source
);
if (result.status === 'success') {
return;
}
if (result.status === 'cancelled') {
return;
}
lastError = result.error || new Error(
'Instagram source verification failed'
);
continue;
}
showMessage('Checking video source...', 5000);
const probe = await probeMediaCandidate(source);
if (!probe.valid) {
console.debug(
'[VRZ V10.4] Rejected direct candidate:',
source,
probe.reason
);
continue;
}
if (probe.kind === 'dash') {
sawDash = true;
continue;
}
if (probe.kind === 'hls') {
await downloadHls(
video,
probe.finalUrl || source.url
);
return;
}
const finalUrl = probe.finalUrl || source.url;
const filename = buildDownloadFilename(
video,
finalUrl,
probe.contentType || source.contentType,
probe.extension || ''
);
const result = await startDirectDownload(
finalUrl,
filename,
source.headers
);
if (result.status === 'success') {
return;
}
// Never continue to another candidate after a Save As
// prompt was cancelled or failed. One click = one prompt.
return;
}
if (source.kind === 'hls') {
checkedCount += 1;
try {
await downloadHls(video, source.url);
return;
} catch (error) {
if (/separate audio\/video/i.test(error?.message || '')) {
sawSeparateTracks = true;
}
throw error;
}
}
if (source.kind === 'blob') {
checkedCount += 1;
const success = await downloadBlobSource(
video,
source.url
);
if (success) {
return;
}
}
if (source.kind === 'dash') {
sawDash = true;
}
} catch (error) {
lastError = error;
if (/separate audio\/video/i.test(error?.message || '')) {
sawSeparateTracks = true;
}
console.warn(
'[VRZ V10.4] Download candidate failed:',
source,
error
);
}
}
if (
platform === 'youtube' &&
openExternalDownloadFallback(
platform,
lastError?.message ||
(sawSeparateTracks ? 'separate audio/video tracks' : '') ||
(sawDash ? 'DASH stream' : '') ||
`no verified source after ${checkedCount} checks`
)
) {
return;
}
if (platform === 'facebook') {
showMessage(
'Facebook source not found yet. Play the visible Story/video once and retry.',
6000
);
return;
}
if (platform === 'instagram') {
showMessage(
'Instagram source not found or was an incomplete fragment. Retry after the video starts playing.',
6500
);
return;
}
if (sawSeparateTracks) {
showMessage('Separate audio/video tracks cannot be merged here');
return;
}
if (sawDash) {
showMessage('DASH/DRM cannot be saved as one file');
return;
}
if (lastError) {
showMessage('Download failed / protected stream');
return;
}
showMessage('No compatible download source found');
} finally {
downloadAttemptActive = false;
}
}
// ========================================================
// RESOLUTION / QUALITY SELECTION
// ========================================================
function getResolutionFromText(value) {
const textValue = String(value || '');
let match = textValue.match(/(?:^|[^0-9])(\d{3,4})\s*p(?:[^0-9]|$)/i);
if (match) {
return {
width: 0,
height: Number(match[1])
};
}
match = textValue.match(/(\d{3,5})\s*[x×]\s*(\d{3,5})/i);
if (match) {
return {
width: Number(match[1]),
height: Number(match[2])
};
}
if (/\b4k\b/i.test(textValue)) {
return {
width: 3840,
height: 2160
};
}
if (/\b2k\b/i.test(textValue)) {
return {
width: 2560,
height: 1440
};
}
return {
width: 0,
height: 0
};
}
function formatBitrate(bitrate) {
const value = Number(bitrate || 0);
if (!value) {
return '';
}
if (value >= 1_000_000) {
return `${(value / 1_000_000).toFixed(value >= 10_000_000 ? 0 : 1)} Mbps`;
}
return `${Math.round(value / 1000)} Kbps`;
}
function formatResolutionLabel({
height = 0,
width = 0,
bitrate = 0,
label = '',
index = 0
} = {}) {
const parsed = getResolutionFromText(label);
const finalHeight = Number(height || parsed.height || 0);
const finalWidth = Number(width || parsed.width || 0);
if (finalHeight > 0) {
return `${Math.round(finalHeight)}P`;
}
if (finalWidth >= 3800) {
return '4K';
}
const cleanLabel = String(label || '')
.replace(/\s+/g, ' ')
.trim();
if (cleanLabel) {
return cleanLabel.toUpperCase();
}
const bitrateLabel = formatBitrate(bitrate);
if (bitrateLabel) {
return bitrateLabel;
}
return `QUALITY ${index + 1}`;
}
function currentVideoResolutionLabel(video) {
if (video?.videoHeight > 0) {
return `${video.videoHeight}P`;
}
return 'RESOLUTION';
}
function uniqueQualityOptions(options) {
const byKey = new Map();
for (const option of options || []) {
if (!option || typeof option.select !== 'function') {
continue;
}
const label = String(option.label || '').trim();
if (!label) {
continue;
}
const key = (
option.id === 'auto'
? 'auto'
: `${option.height || 0}|${label}`
);
const previous = byKey.get(key);
if (
!previous ||
Number(option.bitrate || 0) > Number(previous.bitrate || 0)
) {
byKey.set(key, option);
}
}
return [...byKey.values()].sort((a, b) => {
if (a.id === 'auto') return -1;
if (b.id === 'auto') return 1;
return (
Number(b.height || 0) - Number(a.height || 0) ||
Number(b.bitrate || 0) - Number(a.bitrate || 0)
);
});
}
function addObjectCandidate(list, value) {
if (
value &&
typeof value === 'object' &&
!list.includes(value)
) {
list.push(value);
}
}
function getNearbyPlayerObjects(video) {
const result = [];
let node = video;
let depth = 0;
while (node && depth < 8) {
const propertyNames = [
'hls',
'_hls',
'__hls',
'hlsjs',
'_hlsjs',
'player',
'_player',
'plyr',
'_plyr',
'shakaPlayer',
'_shakaPlayer',
'dashjsPlayer',
'_dashjsPlayer',
'mediaPlayer',
'_mediaPlayer'
];
for (const name of propertyNames) {
try {
addObjectCandidate(result, node[name]);
} catch {
// Ignore getter failures.
}
}
node = getComposedParent(node);
depth += 1;
}
return result;
}
function findHlsInstancesForVideo(video) {
const result = [];
const nearby = getNearbyPlayerObjects(video);
const add = value => {
if (
!value ||
!Array.isArray(value.levels) ||
result.includes(value)
) {
return;
}
result.push(value);
};
for (const value of nearby) {
add(value);
try {
add(value.hls);
add(value._hls);
add(value.hlsjs);
} catch {
// Ignore.
}
}
for (const instance of capturedHlsInstances) {
try {
if (
instance?.media === video ||
instance?._media === video ||
!instance?.media
) {
add(instance);
}
} catch {
// Ignore.
}
}
return result;
}
function createHlsJsQualityAdapter(video) {
const instances = findHlsInstancesForVideo(video);
for (const hls of instances) {
const levels = Array.isArray(hls.levels)
? hls.levels
: [];
if (levels.length < 2) {
continue;
}
const levelOptions = levels.map((level, index) => {
const height = Number(level?.height || 0);
const width = Number(level?.width || 0);
const bitrate = Number(
level?.bitrate ||
level?.averageBitrate ||
0
);
return {
id: `hls-${index}`,
label: formatResolutionLabel({
height,
width,
bitrate,
label: level?.name || '',
index
}),
height,
width,
bitrate,
active: Number(hls.currentLevel) === index,
async select() {
preferredResolutionHeight = height;
try {
hls.currentLevel = index;
} catch {
// Ignore.
}
try {
hls.nextLevel = index;
} catch {
// Ignore.
}
try {
hls.loadLevel = index;
} catch {
// Ignore.
}
}
};
});
const options = uniqueQualityOptions([
{
id: 'auto',
label: 'AUTO',
height: 0,
bitrate: 0,
active: (
Boolean(hls.autoLevelEnabled) ||
Number(hls.currentLevel) < 0
),
async select() {
preferredResolutionHeight = 0;
try {
hls.currentLevel = -1;
} catch {
// Ignore.
}
try {
hls.nextLevel = -1;
} catch {
// Ignore.
}
try {
hls.loadLevel = -1;
} catch {
// Ignore.
}
}
},
...levelOptions
]);
if (options.length >= 3) {
return {
name: 'HLS.JS',
options
};
}
}
return null;
}
function getVideoJsPlayer(video) {
const videojs = PAGE.videojs;
if (!videojs) {
return null;
}
try {
if (typeof videojs.getPlayer === 'function') {
if (video.id) {
const byId = videojs.getPlayer(video.id);
if (byId) {
return byId;
}
}
const container = video.closest?.('.video-js');
if (container?.id) {
const byContainer = videojs.getPlayer(container.id);
if (byContainer) {
return byContainer;
}
}
}
} catch {
// Ignore.
}
return null;
}
function createVideoJsQualityAdapter(video) {
const player = getVideoJsPlayer(video);
if (!player) {
return null;
}
try {
if (typeof player.qualityLevels === 'function') {
const list = player.qualityLevels();
if (list?.length >= 2) {
const levelOptions = [];
for (let index = 0; index < list.length; index += 1) {
const level = list[index];
const height = Number(level?.height || 0);
const width = Number(level?.width || 0);
const bitrate = Number(level?.bitrate || 0);
levelOptions.push({
id: `videojs-level-${index}`,
label: formatResolutionLabel({
height,
width,
bitrate,
label: level?.label || level?.id || '',
index
}),
height,
width,
bitrate,
active: Number(list.selectedIndex) === index,
async select() {
preferredResolutionHeight = height;
for (
let innerIndex = 0;
innerIndex < list.length;
innerIndex += 1
) {
const currentLevel = list[innerIndex];
try {
if (typeof currentLevel.enabled === 'function') {
currentLevel.enabled(innerIndex === index);
} else {
currentLevel.enabled = innerIndex === index;
}
} catch {
// Ignore.
}
}
}
});
}
const options = uniqueQualityOptions([
{
id: 'auto',
label: 'AUTO',
active: Number(list.selectedIndex) < 0,
async select() {
preferredResolutionHeight = 0;
for (
let index = 0;
index < list.length;
index += 1
) {
const level = list[index];
try {
if (typeof level.enabled === 'function') {
level.enabled(true);
} else {
level.enabled = true;
}
} catch {
// Ignore.
}
}
}
},
...levelOptions
]);
if (options.length >= 3) {
return {
name: 'VIDEO.JS',
options
};
}
}
}
} catch {
// Ignore.
}
try {
const tech = player.tech?.(true) || player.tech?.();
const vhs = tech?.vhs || tech?.hls;
const representations = vhs?.representations?.();
if (Array.isArray(representations) && representations.length >= 2) {
const levelOptions = representations.map((representation, index) => {
const height = Number(representation?.height || 0);
const width = Number(representation?.width || 0);
const bitrate = Number(representation?.bandwidth || 0);
return {
id: `vhs-${index}`,
label: formatResolutionLabel({
height,
width,
bitrate,
index
}),
height,
width,
bitrate,
active: false,
async select() {
preferredResolutionHeight = height;
representations.forEach((item, innerIndex) => {
try {
item.enabled(innerIndex === index);
} catch {
// Ignore.
}
});
}
};
});
const options = uniqueQualityOptions([
{
id: 'auto',
label: 'AUTO',
active: false,
async select() {
preferredResolutionHeight = 0;
representations.forEach(item => {
try {
item.enabled(true);
} catch {
// Ignore.
}
});
}
},
...levelOptions
]);
if (options.length >= 3) {
return {
name: 'VIDEO.JS VHS',
options
};
}
}
} catch {
// Ignore.
}
return null;
}
function createShakaQualityAdapter(video) {
const candidates = getNearbyPlayerObjects(video);
try {
addObjectCandidate(
candidates,
video.ui?.getControls?.()?.getPlayer?.()
);
} catch {
// Ignore.
}
for (const player of candidates) {
if (
typeof player?.getVariantTracks !== 'function' ||
typeof player?.selectVariantTrack !== 'function'
) {
continue;
}
try {
const tracks = player.getVariantTracks();
if (!Array.isArray(tracks) || tracks.length < 2) {
continue;
}
const videoTracks = tracks.filter(
track => Number(track?.height || 0) > 0
);
if (videoTracks.length < 2) {
continue;
}
const levelOptions = videoTracks.map((track, index) => {
const height = Number(track?.height || 0);
const width = Number(track?.width || 0);
const bitrate = Number(track?.bandwidth || 0);
return {
id: `shaka-${track?.id ?? index}`,
label: formatResolutionLabel({
height,
width,
bitrate,
index
}),
height,
width,
bitrate,
active: Boolean(track?.active),
async select() {
preferredResolutionHeight = height;
try {
player.configure({
abr: {
enabled: false
}
});
} catch {
// Ignore.
}
player.selectVariantTrack(track, true);
}
};
});
const options = uniqueQualityOptions([
{
id: 'auto',
label: 'AUTO',
active: false,
async select() {
preferredResolutionHeight = 0;
player.configure({
abr: {
enabled: true
}
});
}
},
...levelOptions
]);
if (options.length >= 3) {
return {
name: 'SHAKA',
options
};
}
} catch {
// Ignore.
}
}
return null;
}
function createDashQualityAdapter(video) {
const candidates = getNearbyPlayerObjects(video);
for (const player of candidates) {
if (
typeof player?.getBitrateInfoListFor !== 'function' ||
typeof player?.setQualityFor !== 'function'
) {
continue;
}
try {
const levels = player.getBitrateInfoListFor('video');
if (!Array.isArray(levels) || levels.length < 2) {
continue;
}
const currentQuality = Number(
player.getQualityFor?.('video') ?? -1
);
const levelOptions = levels.map((level, index) => {
const height = Number(level?.height || 0);
const width = Number(level?.width || 0);
const bitrate = Number(level?.bitrate || 0);
return {
id: `dash-${index}`,
label: formatResolutionLabel({
height,
width,
bitrate,
index
}),
height,
width,
bitrate,
active: currentQuality === index,
async select() {
preferredResolutionHeight = height;
try {
player.setAutoSwitchQualityFor?.('video', false);
} catch {
// Ignore.
}
try {
player.updateSettings?.({
streaming: {
abr: {
autoSwitchBitrate: {
video: false
}
}
}
});
} catch {
// Ignore.
}
player.setQualityFor('video', index, true);
}
};
});
const options = uniqueQualityOptions([
{
id: 'auto',
label: 'AUTO',
active: false,
async select() {
preferredResolutionHeight = 0;
try {
player.setAutoSwitchQualityFor?.('video', true);
} catch {
// Ignore.
}
try {
player.updateSettings?.({
streaming: {
abr: {
autoSwitchBitrate: {
video: true
}
}
}
});
} catch {
// Ignore.
}
}
},
...levelOptions
]);
if (options.length >= 3) {
return {
name: 'DASH.JS',
options
};
}
} catch {
// Ignore.
}
}
return null;
}
function createJwQualityAdapter(video) {
if (typeof PAGE.jwplayer !== 'function') {
return null;
}
let node = video;
let depth = 0;
while (node && depth < 10) {
const identity = getElementIdentity(node);
if (
node.id &&
(
identity.includes('jwplayer') ||
identity.includes('jw-')
)
) {
try {
const player = PAGE.jwplayer(node.id);
const levels = player?.getQualityLevels?.();
if (Array.isArray(levels) && levels.length >= 2) {
const current = Number(
player.getCurrentQuality?.() ?? -1
);
const options = uniqueQualityOptions(
levels.map((level, index) => {
const parsed = getResolutionFromText(
`${level?.label || ''} ${level?.name || ''}`
);
return {
id: `jw-${index}`,
label: formatResolutionLabel({
height: parsed.height,
width: parsed.width,
bitrate: level?.bitrate || 0,
label: level?.label || level?.name || '',
index
}),
height: parsed.height,
bitrate: Number(level?.bitrate || 0),
active: current === index,
async select() {
preferredResolutionHeight = parsed.height;
player.setCurrentQuality(index);
}
};
})
);
if (options.length >= 2) {
return {
name: 'JW PLAYER',
options
};
}
}
} catch {
// Ignore.
}
}
node = getComposedParent(node);
depth += 1;
}
return null;
}
function createDPlayerQualityAdapter(video) {
const candidates = getNearbyPlayerObjects(video);
for (const player of candidates) {
const qualities = player?.options?.video?.quality;
if (
typeof player?.switchQuality !== 'function' ||
!Array.isArray(qualities) ||
qualities.length < 2
) {
continue;
}
const currentIndex = Number(
player.qualityIndex ??
player.options?.video?.defaultQuality ??
-1
);
const options = uniqueQualityOptions(
qualities.map((quality, index) => {
const parsed = getResolutionFromText(
`${quality?.name || ''} ${quality?.url || ''}`
);
return {
id: `dplayer-${index}`,
label: formatResolutionLabel({
height: parsed.height,
width: parsed.width,
label: quality?.name || '',
index
}),
height: parsed.height,
bitrate: 0,
active: currentIndex === index,
async select() {
preferredResolutionHeight = parsed.height;
player.switchQuality(index);
}
};
})
);
if (options.length >= 2) {
return {
name: 'DPLAYER',
options
};
}
}
return null;
}
function createArtplayerQualityAdapter(video) {
const candidates = getNearbyPlayerObjects(video);
for (const player of candidates) {
const qualities = (
player?.option?.quality ||
player?.options?.quality
);
if (
typeof player?.switchQuality !== 'function' ||
!Array.isArray(qualities) ||
qualities.length < 2
) {
continue;
}
const options = uniqueQualityOptions(
qualities.map((quality, index) => {
const url = quality?.url || quality?.src || '';
const labelText = (
quality?.html ||
quality?.name ||
quality?.label ||
''
);
const parsed = getResolutionFromText(
`${labelText} ${url}`
);
return {
id: `artplayer-${index}`,
label: formatResolutionLabel({
height: parsed.height,
width: parsed.width,
label: labelText,
index
}),
height: parsed.height,
bitrate: Number(quality?.bitrate || 0),
active: Boolean(quality?.default),
async select() {
preferredResolutionHeight = parsed.height;
await player.switchQuality(
url,
labelText || `${parsed.height || index + 1}`
);
}
};
})
);
if (options.length >= 2) {
return {
name: 'ARTPLAYER',
options
};
}
}
return null;
}
function createPlyrQualityAdapter(video) {
const candidates = getNearbyPlayerObjects(video);
for (const player of candidates) {
const values = player?.config?.quality?.options;
if (!Array.isArray(values) || values.length < 2) {
continue;
}
const numericValues = [
...new Set(
values
.map(value => Number(value))
.filter(value => Number.isFinite(value) && value > 0)
)
];
if (numericValues.length < 2) {
continue;
}
const current = Number(player.quality || 0);
const options = numericValues
.sort((a, b) => b - a)
.map((height, index) => ({
id: `plyr-${height}`,
label: `${height}P`,
height,
bitrate: 0,
active: current === height,
async select() {
preferredResolutionHeight = height;
player.quality = height;
}
}));
return {
name: 'PLYR',
options
};
}
return null;
}
async function switchVideoSourcePreservePlayback(
video,
url,
type = ''
) {
const playbackState = capturePlaybackState(video);
const oldTime = Number(video.currentTime || 0);
const normalized = safeUrl(url);
if (!normalized) {
throw new Error('Invalid video source');
}
rememberMediaUrl(normalized, {
source: 'resolution-switch',
contentType: type
});
const metadataReady = new Promise((resolve, reject) => {
let settled = false;
const cleanup = () => {
video.removeEventListener('loadedmetadata', handleLoaded);
video.removeEventListener('error', handleError);
};
const finish = callback => {
if (settled) {
return;
}
settled = true;
cleanup();
callback();
};
const handleLoaded = () => finish(resolve);
const handleError = () => {
finish(() => reject(new Error('The selected source failed to load')));
};
video.addEventListener('loadedmetadata', handleLoaded, {
once: true
});
video.addEventListener('error', handleError, {
once: true
});
setTimeout(() => finish(resolve), 7000);
});
video.src = normalized;
video.load();
await metadataReady;
try {
if (
Number.isFinite(video.duration) &&
oldTime > 0
) {
video.currentTime = Math.min(
oldTime,
Math.max(0, video.duration - 0.25)
);
}
} catch {
// Ignore seek failures.
}
schedulePlaybackRestore(video, playbackState);
activeVideo = video;
scheduleScan();
}
function getNativeSourceEntries(video) {
const entries = [];
const seen = new Set();
const add = (url, element = null, index = 0) => {
const normalized = safeUrl(url);
if (
!normalized ||
normalized.startsWith('blob:') ||
seen.has(normalized)
) {
return;
}
seen.add(normalized);
const rawHeightValue = [
element?.getAttribute?.('size'),
element?.getAttribute?.('res'),
element?.getAttribute?.('data-quality'),
element?.getAttribute?.('data-resolution'),
element?.getAttribute?.('data-res')
]
.map(value => Number(String(value || '').replace(/[^0-9.]/g, '')))
.find(value => Number.isFinite(value) && value >= 144 && value <= 8640) || 0;
const textValue = [
element?.getAttribute?.('label'),
element?.getAttribute?.('title'),
element?.getAttribute?.('size'),
element?.getAttribute?.('res'),
element?.getAttribute?.('data-quality'),
element?.getAttribute?.('data-resolution'),
element?.getAttribute?.('data-res'),
normalized
].filter(Boolean).join(' ');
const parsed = getResolutionFromText(textValue);
const finalHeight = parsed.height || rawHeightValue;
entries.push({
url: normalized,
type: element?.getAttribute?.('type') || '',
height: finalHeight,
width: parsed.width,
label: formatResolutionLabel({
height: finalHeight,
width: parsed.width,
label:
element?.getAttribute?.('label') ||
element?.getAttribute?.('data-quality') ||
'',
index
})
});
};
[...video.querySelectorAll('source')].forEach((source, index) => {
add(
source.src ||
source.getAttribute('src') ||
source.getAttribute('data-src') ||
source.getAttribute('data-file'),
source,
index
);
});
return entries;
}
function createNativeSourceQualityAdapter(video) {
const entries = getNativeSourceEntries(video);
if (entries.length < 2) {
return null;
}
const options = uniqueQualityOptions(
entries.map((entry, index) => ({
id: `source-${index}`,
label: entry.label,
height: entry.height,
width: entry.width,
bitrate: 0,
active: safeUrl(video.currentSrc) === entry.url,
async select() {
preferredResolutionHeight = entry.height;
await switchVideoSourcePreservePlayback(
video,
entry.url,
entry.type
);
}
}))
);
if (options.length < 2) {
return null;
}
return {
name: 'HTML5 SOURCES',
options
};
}
async function createNativeHlsQualityAdapter(video) {
let nativeHls = false;
try {
nativeHls = Boolean(
video.canPlayType('application/vnd.apple.mpegurl') ||
video.canPlayType('application/x-mpegURL')
);
} catch {
nativeHls = false;
}
if (!nativeHls) {
return null;
}
const manifest = getRankedVideoSources(video)
.find(source => source.kind === 'hls');
if (!manifest) {
return null;
}
try {
const textValue = await fetchText(manifest.url);
const variants = parseHlsMaster(textValue, manifest.url);
if (variants.length < 2) {
return null;
}
const muxedVariants = variants.some(
variant => !variant.hasExternalAudio
)
? variants.filter(variant => !variant.hasExternalAudio)
: variants;
const variantOptions = muxedVariants.map((variant, index) => ({
id: `native-hls-${index}`,
label: formatResolutionLabel({
height: variant.height,
width: variant.width,
bitrate: variant.bandwidth,
label: variant.name,
index
}),
height: variant.height,
width: variant.width,
bitrate: variant.bandwidth,
active: safeUrl(video.currentSrc) === variant.url,
async select() {
preferredResolutionHeight = variant.height;
await switchVideoSourcePreservePlayback(
video,
variant.url,
'application/vnd.apple.mpegurl'
);
}
}));
const options = uniqueQualityOptions([
{
id: 'auto',
label: 'AUTO',
active: safeUrl(video.currentSrc) === manifest.url,
async select() {
preferredResolutionHeight = 0;
await switchVideoSourcePreservePlayback(
video,
manifest.url,
'application/vnd.apple.mpegurl'
);
}
},
...variantOptions
]);
if (options.length >= 3) {
return {
name: 'NATIVE HLS',
options
};
}
} catch {
// Ignore.
}
return null;
}
function scanDomQualityOptions(container) {
if (!container) {
return [];
}
let elements = [];
try {
elements = container.querySelectorAll(
'button, [role="menuitem"], [role="option"], ' +
'[data-quality], [data-resolution], li, a'
);
} catch {
return [];
}
const result = [];
const seen = new Set();
for (const element of elements) {
if (result.length >= 20) {
break;
}
const textValue = [
element.getAttribute?.('aria-label'),
element.getAttribute?.('title'),
element.getAttribute?.('data-quality'),
element.getAttribute?.('data-resolution'),
element.textContent
]
.filter(Boolean)
.join(' ')
.replace(/\s+/g, ' ')
.trim();
const match = textValue.match(
/(?:^|\s)(AUTO|ORIGINAL|SOURCE|\d{3,4}(?:\s*P)?|4K|2K)(?:\s|$)/i
);
if (!match) {
continue;
}
const rawLabel = match[1].replace(/\s+/g, '');
const normalizedLabel = (
/^(auto|original|source)$/i.test(rawLabel)
? 'AUTO'
: (
/^\d{3,4}$/i.test(rawLabel)
? `${rawLabel}P`
: rawLabel.toUpperCase()
)
);
if (seen.has(normalizedLabel)) {
continue;
}
seen.add(normalizedLabel);
const parsed = getResolutionFromText(normalizedLabel);
result.push({
element,
label: normalizedLabel,
height: parsed.height,
active: (
element.getAttribute?.('aria-checked') === 'true' ||
element.getAttribute?.('aria-selected') === 'true' ||
/\b(active|selected|current)\b/i.test(
String(element.className || '')
)
)
});
}
return result;
}
async function createDomQualityAdapter(video) {
const container = findPlayerContainer(video, {
ignoreFullscreen: true
});
if (!container) {
return null;
}
let items = scanDomQualityOptions(container);
if (items.length < 2) {
let qualityControl = null;
try {
qualityControl = [...container.querySelectorAll(
'button, [role="button"], [aria-label], [title]'
)].find(element => {
const textValue = [
element.getAttribute?.('aria-label'),
element.getAttribute?.('title'),
element.textContent
]
.filter(Boolean)
.join(' ')
.replace(/\s+/g, ' ')
.trim();
return /\b(quality|resolution)\b/i.test(textValue);
});
} catch {
qualityControl = null;
}
if (qualityControl) {
try {
qualityControl.click();
await delay(160);
items = scanDomQualityOptions(container);
} catch {
// Ignore.
}
}
}
if (items.length < 2) {
return null;
}
const options = uniqueQualityOptions(
items.map((item, index) => ({
id: `dom-${index}`,
label: item.label,
height: item.height,
bitrate: 0,
active: item.active,
async select() {
preferredResolutionHeight = item.height;
item.element.click();
}
}))
);
if (options.length < 2) {
return null;
}
return {
name: 'PLAYER MENU',
options
};
}
async function findResolutionAdapter(video) {
if (!video) {
return null;
}
const synchronousAdapters = [
createHlsJsQualityAdapter,
createShakaQualityAdapter,
createVideoJsQualityAdapter,
createDashQualityAdapter,
createJwQualityAdapter,
createDPlayerQualityAdapter,
createArtplayerQualityAdapter,
createPlyrQualityAdapter,
createNativeSourceQualityAdapter
];
for (const createAdapter of synchronousAdapters) {
try {
const adapter = createAdapter(video);
if (adapter?.options?.length >= 2) {
return adapter;
}
} catch {
// Continue to the next adapter.
}
}
try {
const nativeHlsAdapter = await createNativeHlsQualityAdapter(video);
if (nativeHlsAdapter?.options?.length >= 2) {
return nativeHlsAdapter;
}
} catch {
// Continue.
}
try {
const domAdapter = await createDomQualityAdapter(video);
if (domAdapter?.options?.length >= 2) {
return domAdapter;
}
} catch {
// Continue.
}
return null;
}
// ========================================================
// UI SHADOW DOM
// ========================================================
const host = document.createElement('div');
host.id = 'vrz-v104-host';
host.setAttribute('data-vrz-current', '1');
setImportant(host, 'position', 'fixed');
setImportant(host, 'top', '50%');
setImportant(host, 'right', '12px');
setImportant(host, 'transform', 'translateY(-50%)');
setImportant(host, 'z-index', '2147483647');
setImportant(host, 'width', 'auto');
setImportant(host, 'height', 'auto');
setImportant(host, 'pointer-events', 'auto');
setImportant(host, 'display', 'none');
setImportant(host, 'opacity', '1');
setImportant(host, 'transition', 'opacity 180ms ease');
const shadow = host.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
* {
box-sizing: border-box;
}
#panel {
position: relative;
width: 220px;
padding: 10px;
background: rgba(10, 10, 10, 0.94);
border: 1px solid rgba(255,255,255,0.45);
border-radius: 12px;
box-shadow: 0 8px 30px rgba(0,0,0,0.65);
font-family: Arial, sans-serif;
color: white;
}
#header {
height: 34px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 8px;
cursor: move;
touch-action: none;
user-select: none;
}
#status {
flex: 1;
color: #00ff88;
font-family: Arial, sans-serif;
font-size: 13px;
font-weight: 700;
line-height: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#minimize {
all: unset;
box-sizing: border-box;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
background: #333;
border-radius: 6px;
color: white;
font-family: Arial, sans-serif;
font-size: 20px;
font-weight: bold;
line-height: 1;
cursor: pointer;
}
#grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
}
.tool-button {
all: unset;
box-sizing: border-box;
width: 100%;
height: 42px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 7px;
background: #181818;
border: 1px solid rgba(255,255,255,0.32);
border-radius: 7px;
color: white;
font-family: Arial, sans-serif;
font-size: 12px;
font-weight: 700;
line-height: 1;
text-align: center;
white-space: nowrap;
overflow: hidden;
cursor: pointer;
user-select: none;
}
.tool-button:hover {
background: #353535;
}
.tool-button:active {
transform: scale(0.96);
}
.wide {
grid-column: span 2;
}
.zoom-row {
grid-column: span 2;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 6px;
}
.zoom-row .tool-button {
padding: 0 4px;
font-size: 11px;
}
#hand[data-active="true"] {
color: #00ff88;
border-color: rgba(0,255,136,0.85);
background: rgba(0, 90, 48, 0.48);
}
.watermark-button {
height: 34px;
color: #00ff88;
border-color: rgba(0,255,136,0.45);
font-size: 11px;
letter-spacing: 0.2px;
}
.watermark-button:hover {
color: white;
border-color: rgba(0,255,136,0.85);
}
#qualityMenu {
display: none;
margin-top: 8px;
padding: 8px;
background: rgba(20, 20, 20, 0.98);
border: 1px solid rgba(255,255,255,0.28);
border-radius: 8px;
}
#qualityHeader {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 7px;
}
#qualityTitle {
color: #00ff88;
font-size: 12px;
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#qualityClose {
all: unset;
box-sizing: border-box;
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border-radius: 6px;
background: #333;
color: white;
font-size: 18px;
line-height: 1;
cursor: pointer;
}
#qualityList {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
max-height: 220px;
overflow-y: auto;
overscroll-behavior: contain;
}
.quality-option {
all: unset;
box-sizing: border-box;
min-height: 36px;
padding: 7px 6px;
display: flex;
align-items: center;
justify-content: center;
background: #181818;
border: 1px solid rgba(255,255,255,0.30);
border-radius: 7px;
color: white;
font-family: Arial, sans-serif;
font-size: 11px;
font-weight: 700;
line-height: 1.2;
text-align: center;
cursor: pointer;
user-select: none;
}
.quality-option:hover {
background: #353535;
}
.quality-option[data-active="true"] {
border-color: #00ff88;
color: #00ff88;
}
.quality-empty {
grid-column: span 2;
padding: 10px 6px;
color: rgba(255,255,255,0.72);
font-size: 11px;
line-height: 1.35;
text-align: center;
}
#mini {
all: unset;
box-sizing: border-box;
width: 48px;
height: 48px;
display: none;
align-items: center;
justify-content: center;
background: rgba(10,10,10,0.92);
border: 2px solid white;
border-radius: 50%;
color: white;
font-family: Arial, sans-serif;
font-size: 22px;
line-height: 1;
cursor: move;
touch-action: none;
user-select: none;
box-shadow: 0 5px 20px rgba(0,0,0,0.6);
}
</style>
<div id="panel">
<div id="header" title="Drag the panel from this area">
<div id="status">0° • 1.00×</div>
<button id="minimize" title="Minimize tools">−</button>
</div>
<div id="grid">
<button class="tool-button" id="rotateLeft">↺ ROTATE LEFT</button>
<button class="tool-button" id="rotateRight">ROTATE RIGHT ↻</button>
<div class="zoom-row">
<button class="tool-button" id="zoomOut">🔍 ZOOM −</button>
<button class="tool-button" id="hand" title="Toggle Hand Tool">✋ HAND</button>
<button class="tool-button" id="zoomIn">🔍 ZOOM +</button>
</div>
<button class="tool-button" id="resolution">RESOLUTION</button>
<button class="tool-button" id="fullscreen">⛶ FULLSCREEN</button>
<button class="tool-button wide" id="download">⬇ DOWNLOAD VIDEO</button>
<button class="tool-button" id="auto">AUTO: OFF</button>
<button class="tool-button" id="reset">RESET ALL</button>
<button
class="tool-button wide watermark-button"
id="watermark"
title="Open @reinaldyalaratte on Instagram">
@reinaldyalaratte
</button>
</div>
<div id="qualityMenu">
<div id="qualityHeader">
<span id="qualityTitle">RESOLUTION</span>
<button id="qualityClose" title="Close resolution menu">×</button>
</div>
<div id="qualityList">
<div class="quality-empty">Detecting resolutions...</div>
</div>
</div>
</div>
<button id="mini" title="Drag or click to open tools">🎬</button>
`;
const panel = shadow.getElementById('panel');
const mini = shadow.getElementById('mini');
const header = shadow.getElementById('header');
const minimizeButton = shadow.getElementById('minimize');
const statusElement = shadow.getElementById('status');
const autoButton = shadow.getElementById('auto');
const handButton = shadow.getElementById('hand');
const resolutionButton = shadow.getElementById('resolution');
const qualityMenu = shadow.getElementById('qualityMenu');
const qualityTitle = shadow.getElementById('qualityTitle');
const qualityList = shadow.getElementById('qualityList');
const qualityClose = shadow.getElementById('qualityClose');
// Transparent interaction layer used only while Hand Tool is active.
// It sits above the player but below this tools panel, so dragging never
// reaches the site's click-to-pause handler.
const handOverlay = document.createElement('div');
handOverlay.id = 'vrz-v104-hand-overlay';
handOverlay.setAttribute('aria-hidden', 'true');
setImportant(handOverlay, 'position', 'fixed');
setImportant(handOverlay, 'left', '0');
setImportant(handOverlay, 'top', '0');
setImportant(handOverlay, 'width', '0');
setImportant(handOverlay, 'height', '0');
setImportant(handOverlay, 'display', 'none');
setImportant(handOverlay, 'background', 'transparent');
setImportant(handOverlay, 'z-index', '2147483646');
setImportant(handOverlay, 'pointer-events', 'none');
setImportant(handOverlay, 'cursor', 'grab');
setImportant(handOverlay, 'touch-action', 'none');
setImportant(handOverlay, 'user-select', 'none');
setImportant(handOverlay, '-webkit-user-select', 'none');
ROOT.appendChild(handOverlay);
// ========================================================
// PREVENT TOOL CLICKS FROM REACHING THE PLAYER
//
// Many players use container clicks to toggle play/pause.
// Without this blocker, Rotate/Zoom clicks may also pause the video.
// ========================================================
const shieldedEvents = [
'click',
'dblclick',
'pointerdown',
'pointerup',
'mousedown',
'mouseup',
'touchstart',
'touchend',
'contextmenu'
];
for (const type of shieldedEvents) {
shadow.addEventListener(type, event => {
event.stopPropagation();
});
}
// ========================================================
// STATUS
// ========================================================
function updateStatus() {
statusElement.textContent = `${angle}° • ${zoom.toFixed(2)}×`;
autoButton.textContent = `AUTO: ${autoRotate ? 'ON' : 'OFF'}`;
}
function showMessage(message, duration = 1800) {
clearTimeout(statusTimer);
statusElement.textContent = message;
statusTimer = setTimeout(updateStatus, duration);
}
function updateResolutionButton(video = getVideo()) {
if (
activeQualityAdapter &&
resolutionVideo === video
) {
const activeOption = activeQualityAdapter.options.find(
option => option.active
);
if (activeOption?.label) {
resolutionButton.textContent = activeOption.label;
return;
}
}
if (preferredResolutionHeight > 0) {
resolutionButton.textContent = `${preferredResolutionHeight}P`;
return;
}
resolutionButton.textContent = currentVideoResolutionLabel(video);
}
function closeResolutionMenu() {
qualityMenuOpen = false;
activeQualityAdapter = null;
qualityMenu.style.display = 'none';
requestAnimationFrame(() => {
clampCurrentHostPosition(true);
});
}
function renderResolutionMenu(adapter, video) {
activeQualityAdapter = adapter;
resolutionVideo = video;
qualityTitle.textContent = adapter?.name
? `RESOLUTION • ${adapter.name}`
: 'RESOLUTION';
qualityList.replaceChildren();
if (!adapter?.options?.length) {
const empty = document.createElement('div');
empty.className = 'quality-empty';
empty.textContent = (
video?.videoHeight > 0
? `Current video: ${video.videoHeight}P. No selectable resolutions were found.`
: 'No selectable resolutions were found.'
);
qualityList.appendChild(empty);
updateResolutionButton(video);
return;
}
for (const option of adapter.options) {
const button = document.createElement('button');
button.className = 'quality-option';
button.textContent = option.label;
button.dataset.active = String(Boolean(option.active));
button.addEventListener('click', async () => {
try {
showMessage(`Switching to ${option.label}...`, 5000);
await option.select();
for (const item of adapter.options) {
item.active = item === option;
}
preferredResolutionHeight = Number(
option.height || 0
);
updateResolutionButton(video);
showMessage(`Resolution: ${option.label}`, 2400);
closeResolutionMenu();
} catch (error) {
console.error(
'[VRZ V10.4] Resolution switch failed:',
error
);
showMessage('Resolution switch failed');
}
});
qualityList.appendChild(button);
}
updateResolutionButton(video);
}
async function openResolutionMenu() {
const video = getVideo();
if (!video) {
showMessage('Video not found');
return;
}
if (
qualityMenuOpen &&
resolutionVideo === video
) {
closeResolutionMenu();
return;
}
qualityMenuOpen = true;
resolutionVideo = video;
activeQualityAdapter = null;
qualityMenu.style.display = 'block';
qualityTitle.textContent = 'RESOLUTION';
qualityList.innerHTML = (
'<div class="quality-empty">Detecting resolutions...</div>'
);
const requestId = ++qualityMenuRequestId;
requestAnimationFrame(() => {
clampCurrentHostPosition(true);
});
try {
const adapter = await findResolutionAdapter(video);
if (
requestId !== qualityMenuRequestId ||
!qualityMenuOpen ||
resolutionVideo !== video
) {
return;
}
renderResolutionMenu(adapter, video);
requestAnimationFrame(() => {
clampCurrentHostPosition(true);
});
} catch (error) {
console.error(
'[VRZ V10.4] Resolution detection failed:',
error
);
if (
requestId === qualityMenuRequestId &&
qualityMenuOpen
) {
renderResolutionMenu(null, video);
}
}
}
// ========================================================
// PANEL POSITION / MINIMIZE
// ========================================================
function normalizeHostPositionForDrag() {
const rect = host.getBoundingClientRect();
setImportant(host, 'left', `${rect.left}px`);
setImportant(host, 'top', `${rect.top}px`);
setImportant(host, 'right', 'auto');
setImportant(host, 'bottom', 'auto');
setImportant(host, 'transform', 'none');
hostWasDragged = true;
return rect;
}
function clampHostPosition(left, top) {
const rect = host.getBoundingClientRect();
const maxLeft = Math.max(
CONFIG.dragMargin,
window.innerWidth - rect.width - CONFIG.dragMargin
);
const maxTop = Math.max(
CONFIG.dragMargin,
window.innerHeight - rect.height - CONFIG.dragMargin
);
return {
left: clamp(left, CONFIG.dragMargin, maxLeft),
top: clamp(top, CONFIG.dragMargin, maxTop)
};
}
function clampCurrentHostPosition(force = false) {
if (
!panelVisible ||
getComputedStyle(host).display === 'none'
) {
return;
}
if (!hostWasDragged && !force) {
return;
}
const rect = host.getBoundingClientRect();
const next = clampHostPosition(rect.left, rect.top);
setImportant(host, 'left', `${next.left}px`);
setImportant(host, 'top', `${next.top}px`);
setImportant(host, 'right', 'auto');
setImportant(host, 'bottom', 'auto');
setImportant(host, 'transform', 'none');
hostWasDragged = true;
}
function getElementCenter(element) {
const rect = element.getBoundingClientRect();
return {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2
};
}
function placeHostSoChildMatchesPoint(child, point) {
if (!child || !point) {
return;
}
const hostRect = host.getBoundingClientRect();
const childRect = child.getBoundingClientRect();
const childCenterX = childRect.left + childRect.width / 2;
const childCenterY = childRect.top + childRect.height / 2;
const next = clampHostPosition(
hostRect.left + (point.x - childCenterX),
hostRect.top + (point.y - childCenterY)
);
setImportant(host, 'left', `${next.left}px`);
setImportant(host, 'top', `${next.top}px`);
setImportant(host, 'right', 'auto');
setImportant(host, 'bottom', 'auto');
setImportant(host, 'transform', 'none');
hostWasDragged = true;
}
function minimizePanel() {
closeResolutionMenu();
// Keep the mini button exactly where the panel's top-right
// minimize button was. This makes rapid mini/maxi clicking easy.
const anchorPoint = getElementCenter(minimizeButton);
normalizeHostPositionForDrag();
panelMinimized = true;
panel.style.display = 'none';
mini.style.display = 'flex';
requestAnimationFrame(() => {
placeHostSoChildMatchesPoint(mini, anchorPoint);
});
}
function restorePanel() {
// Keep the panel's minimize button exactly where the mini button
// was. The full panel therefore expands to the LEFT of the button.
const anchorPoint = getElementCenter(mini);
normalizeHostPositionForDrag();
panelMinimized = false;
panel.style.display = 'block';
mini.style.display = 'none';
requestAnimationFrame(() => {
placeHostSoChildMatchesPoint(minimizeButton, anchorPoint);
requestAnimationFrame(() => {
placeHostSoChildMatchesPoint(minimizeButton, anchorPoint);
});
});
}
function togglePanel() {
if (!panelVisible) {
return;
}
if (panelMinimized) {
restorePanel();
} else {
minimizePanel();
}
}
// ========================================================
// DRAG PANEL
// ========================================================
const dragState = {
active: false,
moved: false,
startX: 0,
startY: 0,
startLeft: 0,
startTop: 0,
pointerId: null,
source: null
};
function beginDrag(event, source) {
if (
event.button !== undefined &&
event.button !== 0
) {
return;
}
if (
source === 'header' &&
event.target === minimizeButton
) {
return;
}
const rect = normalizeHostPositionForDrag();
dragState.active = true;
dragState.moved = false;
dragState.startX = event.clientX;
dragState.startY = event.clientY;
dragState.startLeft = rect.left;
dragState.startTop = rect.top;
dragState.pointerId = event.pointerId;
dragState.source = source;
event.currentTarget.setPointerCapture?.(event.pointerId);
event.preventDefault();
showToolsFromActivity();
}
function moveDrag(event) {
if (!dragState.active) {
return;
}
const deltaX = event.clientX - dragState.startX;
const deltaY = event.clientY - dragState.startY;
if (
Math.abs(deltaX) > 3 ||
Math.abs(deltaY) > 3
) {
dragState.moved = true;
}
const next = clampHostPosition(
dragState.startLeft + deltaX,
dragState.startTop + deltaY
);
setImportant(host, 'left', `${next.left}px`);
setImportant(host, 'top', `${next.top}px`);
}
function endDrag(event) {
if (!dragState.active) {
return;
}
const source = dragState.source;
const moved = dragState.moved;
dragState.active = false;
dragState.source = null;
event.currentTarget.releasePointerCapture?.(dragState.pointerId);
if (source === 'mini' && !moved) {
restorePanel();
}
scheduleFullscreenIdleHide();
}
header.addEventListener('pointerdown', event => beginDrag(event, 'header'));
header.addEventListener('pointermove', moveDrag);
header.addEventListener('pointerup', endDrag);
header.addEventListener('pointercancel', endDrag);
mini.addEventListener('pointerdown', event => beginDrag(event, 'mini'));
mini.addEventListener('pointermove', moveDrag);
mini.addEventListener('pointerup', endDrag);
mini.addEventListener('pointercancel', endDrag);
// ========================================================
// HAND TOOL — DRAG THE ZOOMED / ROTATED VIDEO
// ========================================================
const handDragState = {
active: false,
pointerId: null,
startX: 0,
startY: 0,
startPanX: 0,
startPanY: 0,
video: null,
playbackState: null
};
function getHandVideo() {
if (
transformedVideo &&
transformedVideo.isConnected
) {
return transformedVideo;
}
return getVideo();
}
function mountHandOverlay() {
const fullscreenElement = getFullscreenElement();
const targetParent = (
fullscreenElement &&
!(fullscreenElement instanceof HTMLVideoElement)
? fullscreenElement
: ROOT
);
if (
targetParent &&
handOverlay.parentElement !== targetParent
) {
targetParent.appendChild(handOverlay);
}
}
function hideHandOverlay() {
setImportant(handOverlay, 'display', 'none');
setImportant(handOverlay, 'pointer-events', 'none');
}
function syncHandOverlay() {
if (!handToolActive || !panelVisible) {
hideHandOverlay();
return;
}
const video = getHandVideo();
if (!video || !video.isConnected) {
hideHandOverlay();
return;
}
const container = findTransformContainer(video);
if (!container || !container.isConnected) {
hideHandOverlay();
return;
}
let rect;
try {
rect = container.getBoundingClientRect();
} catch {
hideHandOverlay();
return;
}
const left = Math.max(0, rect.left);
const top = Math.max(0, rect.top);
const right = Math.min(window.innerWidth, rect.right);
const bottom = Math.min(window.innerHeight, rect.bottom);
const width = Math.max(0, right - left);
const height = Math.max(0, bottom - top);
if (width < 2 || height < 2) {
hideHandOverlay();
return;
}
mountHandOverlay();
setImportant(handOverlay, 'left', `${left}px`);
setImportant(handOverlay, 'top', `${top}px`);
setImportant(handOverlay, 'width', `${width}px`);
setImportant(handOverlay, 'height', `${height}px`);
setImportant(handOverlay, 'display', 'block');
setImportant(handOverlay, 'pointer-events', 'auto');
setImportant(
handOverlay,
'cursor',
handDragState.active ? 'grabbing' : 'grab'
);
}
function updateHandToolUi() {
handButton.dataset.active = handToolActive ? 'true' : 'false';
handButton.setAttribute(
'aria-pressed',
handToolActive ? 'true' : 'false'
);
}
function cancelHandDrag() {
if (handTransformFrame !== null) {
cancelAnimationFrame(handTransformFrame);
handTransformFrame = null;
}
handDragState.active = false;
handDragState.pointerId = null;
handDragState.video = null;
handDragState.playbackState = null;
setImportant(handOverlay, 'cursor', 'grab');
}
function setHandToolActive(enabled, options = {}) {
const next = Boolean(enabled);
if (next) {
const video = getVideo();
if (!video) {
handToolActive = false;
updateHandToolUi();
hideHandOverlay();
if (!options.silent) {
showMessage('Video not found');
}
return;
}
activeVideo = video;
handToolActive = true;
updateHandToolUi();
syncHandOverlay();
if (!options.silent) {
showMessage('Hand Tool ON');
}
return;
}
handToolActive = false;
cancelHandDrag();
updateHandToolUi();
hideHandOverlay();
if (!options.silent) {
showMessage('Hand Tool OFF');
}
}
function toggleHandTool() {
setHandToolActive(!handToolActive);
}
function scheduleHandTransform() {
if (handTransformFrame !== null) {
return;
}
handTransformFrame = requestAnimationFrame(() => {
handTransformFrame = null;
applyTransform({
preservePlayback: false,
skipStabilize: true
});
});
}
function beginHandDrag(event) {
if (
!handToolActive ||
(event.button !== undefined && event.button !== 0)
) {
return;
}
const video = getHandVideo();
if (!video) {
return;
}
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation?.();
handDragState.active = true;
handDragState.pointerId = event.pointerId;
handDragState.startX = event.clientX;
handDragState.startY = event.clientY;
handDragState.startPanX = panX;
handDragState.startPanY = panY;
handDragState.video = video;
handDragState.playbackState = capturePlaybackState(video);
handOverlay.setPointerCapture?.(event.pointerId);
setImportant(handOverlay, 'cursor', 'grabbing');
if (angle === 0 && zoom <= 1) {
showMessage('Zoom in, then drag');
}
showToolsFromActivity();
}
function moveHandDrag(event) {
if (
!handDragState.active ||
event.pointerId !== handDragState.pointerId
) {
return;
}
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation?.();
panX = handDragState.startPanX + (
event.clientX - handDragState.startX
);
panY = handDragState.startPanY + (
event.clientY - handDragState.startY
);
scheduleHandTransform();
}
function endHandDrag(event) {
if (
!handDragState.active ||
(
event.pointerId !== undefined &&
event.pointerId !== handDragState.pointerId
)
) {
return;
}
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation?.();
const video = handDragState.video;
const playbackState = handDragState.playbackState;
const pointerId = handDragState.pointerId;
handDragState.active = false;
handDragState.pointerId = null;
handDragState.video = null;
handDragState.playbackState = null;
handOverlay.releasePointerCapture?.(pointerId);
setImportant(handOverlay, 'cursor', 'grab');
// One final render clamps the pan offsets to the visible video
// bounds, preventing the user from accidentally losing the video.
applyTransform({
preservePlayback: false,
skipStabilize: true
});
schedulePlaybackRestore(video, playbackState);
scheduleTransformStabilization(video);
scheduleFullscreenIdleHide();
}
handOverlay.addEventListener('pointerdown', beginHandDrag, true);
handOverlay.addEventListener('pointermove', moveHandDrag, true);
handOverlay.addEventListener('pointerup', endHandDrag, true);
handOverlay.addEventListener('pointercancel', endHandDrag, true);
for (const type of ['click', 'dblclick', 'contextmenu']) {
handOverlay.addEventListener(type, event => {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation?.();
}, true);
}
// ========================================================
// FULLSCREEN AUTO-HIDE AFTER 3 SECONDS
// ========================================================
function isFullscreenMode() {
return Boolean(getFullscreenElement());
}
function showToolsImmediately() {
clearTimeout(fullscreenIdleTimer);
setImportant(host, 'opacity', '1');
setImportant(host, 'pointer-events', 'auto');
}
function hideToolsForFullscreenIdle() {
if (
!isFullscreenMode() ||
!panelVisible ||
dragState.active
) {
return;
}
setImportant(host, 'opacity', '0');
setImportant(host, 'pointer-events', 'none');
}
function scheduleFullscreenIdleHide() {
clearTimeout(fullscreenIdleTimer);
if (!isFullscreenMode() || !panelVisible) {
showToolsImmediately();
return;
}
fullscreenIdleTimer = setTimeout(
hideToolsForFullscreenIdle,
CONFIG.fullscreenIdleMs
);
}
function showToolsFromActivity() {
if (!isFullscreenMode()) {
return;
}
showToolsImmediately();
scheduleFullscreenIdleHide();
}
const activityEvents = [
'pointermove',
'pointerdown',
'mousemove',
'touchstart',
'wheel',
'keydown'
];
for (const type of activityEvents) {
document.addEventListener(
type,
showToolsFromActivity,
true
);
}
// ========================================================
// BUTTON EVENTS
// ========================================================
shadow.getElementById('rotateLeft').addEventListener('click', rotateLeft);
shadow.getElementById('rotateRight').addEventListener('click', rotateRight);
shadow.getElementById('zoomOut').addEventListener('click', zoomOut);
handButton.addEventListener('click', toggleHandTool);
shadow.getElementById('zoomIn').addEventListener('click', zoomIn);
resolutionButton.addEventListener('click', openResolutionMenu);
shadow.getElementById('fullscreen').addEventListener('click', toggleFullscreen);
shadow.getElementById('download').addEventListener('click', downloadVideo);
shadow.getElementById('reset').addEventListener('click', resetEverything);
shadow.getElementById('watermark').addEventListener('click', () => {
const instagramUrl = 'https://www.instagram.com/reinaldyalaratte/';
try {
window.open(
instagramUrl,
'_blank',
'noopener,noreferrer'
);
} catch {
// A normal user click should allow a new tab. This fallback is
// only for browsers that reject the feature string.
window.open(instagramUrl, '_blank');
}
});
shadow.getElementById('auto').addEventListener('click', () => {
autoRotate = !autoRotate;
updateStatus();
if (autoRotate) {
const video = getVideo();
if (video) {
tryAutoRotate(video);
}
}
});
minimizeButton.addEventListener('click', event => {
event.stopPropagation();
minimizePanel();
});
qualityClose.addEventListener('click', event => {
event.stopPropagation();
closeResolutionMenu();
});
// ========================================================
// MOUNT + VISIBILITY
// ========================================================
function mountUI() {
const fullscreenElement = getFullscreenElement();
if (fullscreenElement) {
if (fullscreenElement instanceof HTMLVideoElement) {
repairDirectVideoFullscreen();
return;
}
if (host.parentElement !== fullscreenElement) {
fullscreenElement.appendChild(host);
}
mountHandOverlay();
requestAnimationFrame(() => {
clampCurrentHostPosition(true);
syncHandOverlay();
});
return;
}
if (host.parentElement !== ROOT) {
ROOT.appendChild(host);
}
mountHandOverlay();
requestAnimationFrame(() => {
clampCurrentHostPosition(false);
syncHandOverlay();
});
}
function updatePanelVisibility() {
const video = getVideo();
const shouldShow = Boolean(video);
if (shouldShow) {
const becomingVisible = !panelVisible;
mountUI();
if (becomingVisible) {
setImportant(host, 'display', 'block');
panelVisible = true;
}
if (panelMinimized) {
panel.style.display = 'none';
mini.style.display = 'flex';
} else {
panel.style.display = 'block';
mini.style.display = 'none';
}
if (isFullscreenMode()) {
if (becomingVisible) {
showToolsImmediately();
scheduleFullscreenIdleHide();
}
} else {
showToolsImmediately();
}
syncHandOverlay();
return;
}
activeVideo = null;
panelVisible = false;
preferredResolutionHeight = 0;
closeResolutionMenu();
clearTimeout(fullscreenIdleTimer);
setImportant(host, 'display', 'none');
hideHandOverlay();
if (transformedVideo || currentContainer) {
restoreAllStyles();
}
}
// ========================================================
// REGISTER VIDEO
// ========================================================
function activateVideo(video) {
if (!isVideoVisible(video)) {
return;
}
const videoChanged = activeVideo !== video;
activeVideo = video;
if (videoChanged) {
preferredResolutionHeight = 0;
resolutionVideo = video;
closeResolutionMenu();
updateResolutionButton(video);
}
for (const url of getVideoSourceCandidates(video)) {
rememberMediaUrl(url, {
source: 'video-activate',
contentType: getVideoMimeType(video)
});
}
updatePanelVisibility();
}
function registerVideo(video) {
if (
!video ||
registeredVideos.has(video)
) {
return;
}
registeredVideos.add(video);
const activate = () => activateVideo(video);
video.addEventListener('pointerenter', activate, true);
video.addEventListener('mouseenter', activate, true);
video.addEventListener('touchstart', activate, { passive: true });
video.addEventListener('click', activate, true);
const mediaEvents = [
'play',
'playing',
'loadedmetadata',
'loadeddata',
'canplay',
'durationchange',
'progress',
'resize',
'timeupdate'
];
for (const eventName of mediaEvents) {
video.addEventListener(eventName, () => {
activateVideo(video);
if (
eventName === 'loadedmetadata' ||
eventName === 'resize' ||
eventName === 'canplay'
) {
updateResolutionButton(video);
}
if (
eventName === 'play' ||
eventName === 'playing' ||
eventName === 'loadedmetadata'
) {
tryAutoRotate(video);
}
}, true);
}
video.addEventListener('emptied', scheduleScan, true);
video.addEventListener('pause', scheduleScan, true);
video.addEventListener('ended', scheduleScan, true);
if (video.readyState >= 1) {
activateVideo(video);
tryAutoRotate(video);
}
}
// Capture videos from composedPath, including open/closed Shadow DOM
// roots captured since document-start.
const pointerActivationEvents = [
'pointerover',
'pointerdown',
'click',
'touchstart'
];
for (const eventName of pointerActivationEvents) {
document.addEventListener(eventName, event => {
const path = event.composedPath?.() || [];
const video = path.find(
node => node instanceof HTMLVideoElement
);
if (video) {
activateVideo(video);
}
}, true);
}
// ========================================================
// OBSERVER + SCAN
// ========================================================
function observeRoot(root) {
if (!root || observedRoots.has(root)) {
return;
}
observedRoots.add(root);
try {
const observer = new MutationObserver(scheduleScan);
observer.observe(root, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: [
'src',
'style',
'class',
'hidden',
'poster',
'controls'
]
});
} catch {
// Some roots may not support observation.
}
}
function scanVideos() {
clearTimeout(scanTimer);
scanTimer = null;
const roots = getAllSearchRoots();
for (const root of roots) {
observeRoot(root);
}
const videos = getAllVideos();
for (const video of videos) {
registerVideo(video);
}
if (
!activeVideo ||
!isVideoVisible(activeVideo)
) {
activeVideo = findBestVideo();
}
// Keep the host attached to the fullscreen subtree.
mountUI();
updatePanelVisibility();
}
function scheduleScan() {
if (!uiStarted) {
return;
}
clearTimeout(scanTimer);
scanTimer = setTimeout(
scanVideos,
CONFIG.scanDebounce
);
}
externalScheduleScan = scheduleScan;
// ========================================================
// FULLSCREEN CHANGE
// ========================================================
function handleFullscreenChange() {
const video = getVideo();
const playbackState = capturePlaybackState(video);
showToolsImmediately();
const delays = [0, 20, 100, 350, 800, 1500];
for (const wait of delays) {
setTimeout(() => {
mountUI();
updatePanelVisibility();
if (angle !== 0 || zoom !== 1 || panX !== 0 || panY !== 0) {
applyTransform({ preservePlayback: true });
}
clampCurrentHostPosition(true);
}, wait);
}
if (isFullscreenMode()) {
scheduleFullscreenIdleHide();
} else {
clearTimeout(fullscreenIdleTimer);
showToolsImmediately();
}
schedulePlaybackRestore(video, playbackState);
}
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
// ========================================================
// RESIZE / SCROLL / VISIBILITY
// ========================================================
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
clampCurrentHostPosition(true);
if (angle !== 0 || zoom !== 1 || panX !== 0 || panY !== 0) {
applyTransform({ preservePlayback: true });
}
scanVideos();
}, 160);
});
document.addEventListener('scroll', () => {
syncHandOverlay();
scheduleScan();
}, true);
document.addEventListener('visibilitychange', scheduleScan, true);
// ========================================================
// HOTKEYS
// ========================================================
document.addEventListener('keydown', event => {
if (!panelVisible) {
return;
}
const key = event.key.toLowerCase();
let handled = false;
if (event.shiftKey && key === 'r') {
rotateRight();
handled = true;
}
if (event.shiftKey && key === 'l') {
rotateLeft();
handled = true;
}
if (
event.shiftKey &&
(event.key === '+' || event.code === 'NumpadAdd')
) {
zoomIn();
handled = true;
}
if (
event.shiftKey &&
(
event.key === '_' ||
event.key === '-' ||
event.code === 'NumpadSubtract'
)
) {
zoomOut();
handled = true;
}
if (event.shiftKey && key === 'z') {
resetZoom();
handled = true;
}
if (event.shiftKey && key === 'v') {
togglePanel();
handled = true;
}
if (event.shiftKey && key === 'd') {
downloadVideo();
handled = true;
}
if (handled) {
event.preventDefault();
event.stopImmediatePropagation();
showToolsFromActivity();
}
}, true);
// ========================================================
// START
// ========================================================
mountUI();
updateStatus();
scanVideos();
setInterval(() => {
scanVideos();
if (isFullscreenMode()) {
mountUI();
}
}, CONFIG.scanInterval);
console.log('[Universal Video Rotate + Zoom V10.4] ACTIVE');
}
bootWhenReady();
})();