Enables downloads & replaces site player with native HTML5 player + controls
// ==UserScript==
// @name GPT - Player / Downloader
// @namespace https://github.com/BD9Max/userscripts
// @version 1.2
// @description Enables downloads & replaces site player with native HTML5 player + controls
// @author krbd9max
// @license MIT
// @match https://*.gayporntube.com/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
function restoreNativePlayer() {
const video = document.getElementById('play-video') || document.querySelector('video');
if (!video || video.dataset.nativeRestored) return;
video.dataset.nativeRestored = '1';
// 1. Try to destroy Fluid Player instance if it exists
try {
if (typeof fluidPlayer !== 'undefined' && window.fluidPlayerInstances) {
// some versions store instances
Object.values(window.fluidPlayerInstances || {}).forEach(p => {
try { p.destroy && p.destroy(); } catch (e) {}
});
}
// also try common global
if (video.fluidPlayer) {
try { video.fluidPlayer.destroy(); } catch (e) {}
}
} catch (e) {}
// 2. Clean source URL (remove ?br=xxxx)
const source = video.querySelector('source');
if (source && source.src) {
const clean = source.src.replace(/\/\?br=\d*$/, '').replace(/\?br=\d*$/, '');
if (clean !== source.src) {
source.src = clean;
}
}
// 3. Unwrap video from Fluid Player wrapper if present
const wrapper = video.closest('.fluid_video_wrapper') || video.parentElement;
if (wrapper && wrapper !== video && wrapper.classList.contains('fluid_video_wrapper')) {
// Move video out of the wrapper
wrapper.parentNode.insertBefore(video, wrapper);
// Remove the entire Fluid Player DOM
wrapper.remove();
}
// 4. Force native controls + download
video.controls = true;
video.setAttribute('controls', '');
video.removeAttribute('controlsList');
video.controlsList = '';
video.setAttribute('preload', 'auto');
video.removeAttribute('width');
video.removeAttribute('height');
// 5. Force visibility and proper size
Object.assign(video.style, {
display: 'block',
visibility: 'visible',
opacity: '1',
width: '100%',
height: 'auto',
maxHeight: '80vh',
position: 'relative',
zIndex: '9999',
background: '#000',
margin: '0 auto'
});
// 6. Make sure parent containers don’t collapse
let parent = video.parentElement;
for (let i = 0; i < 5 && parent; i++) {
parent.style.display = 'block';
parent.style.visibility = 'visible';
parent.style.height = 'auto';
parent.style.minHeight = '300px';
parent.style.overflow = 'visible';
parent = parent.parentElement;
}
// 7. Hide any leftover Fluid Player elements that might still be around
document.querySelectorAll(
'.fluid_controls_container, .fluid_control_bar, .fluid_initial_play_button, ' +
'.fluid_big_play_button, .fluid_context_menu, [class*="fluid_"]'
).forEach(el => {
if (!el.contains(video)) {
el.style.display = 'none';
el.remove();
}
});
// 8. Reload so the clean source takes effect
video.load();
console.log('[Native Player] Successfully restored native HTML5 player');
}
// Run multiple times because the player is created asynchronously
function start() {
restoreNativePlayer();
setTimeout(restoreNativePlayer, 800);
setTimeout(restoreNativePlayer, 2000);
setTimeout(restoreNativePlayer, 4000);
}
start();
// Keep watching for late injection
const obs = new MutationObserver(() => {
if (document.getElementById('play-video') && !document.getElementById('play-video').dataset.nativeRestored) {
restoreNativePlayer();
}
});
obs.observe(document.documentElement, { childList: true, subtree: true });
})();